From a848b48e69e80e1d99c88251c62ef40fb3b623e4 Mon Sep 17 00:00:00 2001 From: jacob berkman Date: Sat, 27 Aug 2016 19:34:58 -0700 Subject: [PATCH 001/342] Correct spelling error --- moduleManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moduleManager.cs b/moduleManager.cs index 49e71746..56845a08 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -822,7 +822,7 @@ private IEnumerator ProcessPatch(bool blocking) #endif // TODO : Remove if we ever get a way to load sooner - log("Reloading ressources definitions"); + log("Reloading resources definitions"); PartResourceLibrary.Instance.LoadDefinitions(); foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks) From 1e685c87a12dfe7894462927a52799d3d5ae7de3 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 5 Oct 2016 22:33:13 +0200 Subject: [PATCH 002/342] Getting ready for 1.2 - Better cat and Text --- CatMover.cs | 93 ++++++++ CopyLocalFalse.txt | 2 + ModuleManager.csproj | 52 ++++- Properties/Resources.Designer.cs | 104 ++++++++- Properties/Resources.resx | 40 +++- Properties/cat | Bin 872 -> 0 bytes Properties/cat-1.png | Bin 0 -> 721 bytes Properties/cat-10.png | Bin 0 -> 701 bytes Properties/cat-11.png | Bin 0 -> 707 bytes Properties/cat-12.png | Bin 0 -> 721 bytes Properties/cat-2.png | Bin 0 -> 738 bytes Properties/cat-3.png | Bin 0 -> 704 bytes Properties/cat-4.png | Bin 0 -> 701 bytes Properties/cat-5.png | Bin 0 -> 707 bytes Properties/cat-6.png | Bin 0 -> 721 bytes Properties/cat-7.png | Bin 0 -> 721 bytes Properties/cat-8.png | Bin 0 -> 732 bytes Properties/cat-9.png | Bin 0 -> 704 bytes Properties/rainbow2.png | Bin 0 -> 240 bytes moduleManager.cs | 383 ++++++++++++++++++------------- packages.config | 4 + 21 files changed, 509 insertions(+), 169 deletions(-) create mode 100644 CatMover.cs create mode 100644 CopyLocalFalse.txt delete mode 100644 Properties/cat create mode 100644 Properties/cat-1.png create mode 100644 Properties/cat-10.png create mode 100644 Properties/cat-11.png create mode 100644 Properties/cat-12.png create mode 100644 Properties/cat-2.png create mode 100644 Properties/cat-3.png create mode 100644 Properties/cat-4.png create mode 100644 Properties/cat-5.png create mode 100644 Properties/cat-6.png create mode 100644 Properties/cat-7.png create mode 100644 Properties/cat-8.png create mode 100644 Properties/cat-9.png create mode 100644 Properties/rainbow2.png create mode 100644 packages.config diff --git a/CatMover.cs b/CatMover.cs new file mode 100644 index 00000000..12cba257 --- /dev/null +++ b/CatMover.cs @@ -0,0 +1,93 @@ +using System.Collections; +using UnityEngine; + +namespace ModuleManager +{ + public class CatMover : MonoBehaviour + { + public Vector3 spos; + + public float vel = 5; + private float offsetY; + + public TrailRenderer trail; + + public Sprite[] frames; + public float secFrame = 0.07f; + + private SpriteRenderer spriteRenderer; + private int spriteIdx; + + private int totalLenth = 100; + private float activePos = 0; + + public float scale = 2; + + + private float time = 5; + private float trailTime = 0.5f; + + + // Use this for initialization + void Start() + { + trail = this.GetComponent(); + spriteRenderer = this.GetComponent(); + + spriteRenderer.sortingOrder = 3; + trail.sortingOrder = 2; + + offsetY = Mathf.FloorToInt(0.2f * Screen.height); + + spos.z = -1; + + StartCoroutine(Animate()); + + trailTime = time / 4; + + totalLenth = (int) (Screen.width / time * trail.time) + 150; + trail.time = trailTime; + } + + void Update() + { + if (trail.time <= 0f) + { + trail.time = trailTime; + } + + activePos += ((Screen.width / time) * Time.deltaTime); + + if (activePos > (Screen.width + totalLenth)) + { + activePos = -frames[spriteIdx].textureRect.width; + trail.time = 0;; + } + + float f = 2f * Mathf.PI * (activePos) / (Screen.width * 0.5f); + + float heightOffset = Mathf.Sin(f) * (frames[spriteIdx].textureRect.height * scale); + + spos.x = activePos; + spos.y = offsetY + heightOffset; + + transform.position = KSP.UI.UIMainCamera.Camera.ScreenToWorldPoint(spos); + transform.rotation = Quaternion.Euler(0, 0, Mathf.Cos(f) * 0.25f * Mathf.PI * Mathf.Rad2Deg); + } + + IEnumerator Animate() + { + if (frames.Length == 0) + yield return null; + + WaitForSeconds yield = new WaitForSeconds(secFrame); + + while (true) + { + spriteIdx = (spriteIdx+1) % frames.Length; + spriteRenderer.sprite = frames[spriteIdx]; + yield return yield; + } + } + } +} diff --git a/CopyLocalFalse.txt b/CopyLocalFalse.txt new file mode 100644 index 00000000..d5a69533 --- /dev/null +++ b/CopyLocalFalse.txt @@ -0,0 +1,2 @@ +Copy Local has been set to false for all dependencies. +Remark by Jan Van der Haegen: This file has been added because Nuget would installs packages to the solution instead of the project if there are only PowerShell scripts... (So yes: it's safe to remove this file!) \ No newline at end of file diff --git a/ModuleManager.csproj b/ModuleManager.csproj index e4b787a9..1acb75ac 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -31,6 +31,7 @@ False + @@ -41,24 +42,69 @@ False False - + False False + + False + C:\Games\KSPSteamController\KSPSteamCtrlr\KSPUnity-Steam-Symlinks\UnityEngine.UI.dll + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + echo copying to "%25KSPDIR%25\GameData\" -copy "$(TargetPath)" "C:\Games\ksp-win_dev\GameData\" +"C:\Games\Tools\pdb2mdb\pdb2mdb.exe" $(TargetFileName) +xcopy /Y "$(TargetPath)" "C:\Games\ksp-win_dev\GameData\" +xcopy /Y "$(TargetDir)$(TargetName).pdb" "C:\Games\ksp-win_dev\GameData\" +xcopy /Y "$(TargetDir)$(TargetName).dll.mdb" "C:\Games\ksp-win_dev\GameData\" del "C:\Games\ksp-win_dev\GameData\ModuleManager.ConfigCache" \ No newline at end of file diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index 91db7ae0..43d089d5 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -63,11 +63,111 @@ internal Resources() { /// /// Looks up a localized resource of type System.Byte[]. /// - internal static byte[] cat { + internal static byte[] cat1 { get { - object obj = ResourceManager.GetObject("cat", resourceCulture); + object obj = ResourceManager.GetObject("cat-1", resourceCulture); return ((byte[])(obj)); } } + + internal static byte[] cat2 + { + get + { + object obj = ResourceManager.GetObject("cat-2", resourceCulture); + return ((byte[])(obj)); + } + } + + internal static byte[] cat3 + { + get + { + object obj = ResourceManager.GetObject("cat-3", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat4 + { + get + { + object obj = ResourceManager.GetObject("cat-4", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat5 + { + get + { + object obj = ResourceManager.GetObject("cat-5", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat6 + { + get + { + object obj = ResourceManager.GetObject("cat-6", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat7 + { + get + { + object obj = ResourceManager.GetObject("cat-7", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat8 + { + get + { + object obj = ResourceManager.GetObject("cat-8", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat9 + { + get + { + object obj = ResourceManager.GetObject("cat-9", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat10 + { + get + { + object obj = ResourceManager.GetObject("cat-10", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat11 + { + get + { + object obj = ResourceManager.GetObject("cat-11", resourceCulture); + return ((byte[])(obj)); + } + } + internal static byte[] cat12 + { + get + { + object obj = ResourceManager.GetObject("cat-12", resourceCulture); + return ((byte[])(obj)); + } + } + + internal static byte[] rainbow + { + get + { + object obj = ResourceManager.GetObject("rainbow2", resourceCulture); + return ((byte[])(obj)); + } + } + } } diff --git a/Properties/Resources.resx b/Properties/Resources.resx index 938b3351..f8a77f25 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -118,7 +118,43 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - cat;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + cat-1.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-10.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-11.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-12.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-2.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-3.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-4.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-5.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-6.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-7.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-8.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + cat-9.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + rainbow2.png;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 \ No newline at end of file diff --git a/Properties/cat b/Properties/cat deleted file mode 100644 index 0fb30459b7fde2be10400c81f66d2aeb9c66d9fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 872 zcmV-u1DE`XP)004R= z004l4008;_004mL004C`008P>0026e000+nl3&F}00009a7bBm000XT000XT0n*)m z`~Uy|7<5HgbW?9;ba!ELWdLwtX>N2bZe?^JG%heMGmPe!Pyhe|%1J~)R7gvumCKG2 zK@^6I2F*ZX;#(M8Fo}~8pU9<80DB~H#@le|VPLolIFPWAS-5lSM5FNuOkA>XDGnUZ zUtQHycUL!KJed!w>YS?o=k#U7Z6fWYDeXA_g*wU}oTVgCa&z4z0^g2_zWyWEuH1V! zlg3&0Fa^UPO+O}{fiJI#_>XuPpu;pQW6k$xz7bE(5|m+~v>l{<6LlQA4GMrqta{;% z*M|*SU#z1>WJi)rG+8JHq@@Wh{_q7+Hzc5$qIqi12cE--G_Lq`W|}a^JhF&ukPetg z0JW~`QnT5lLZLwOr*oqGOQM0QQ6`GTBDGpAS%*2JOq>hac0DlGAf0I9Y`2bDZ35(S zIa<|Mw0foZcuEHcS(;2Hehv1(1FHzyYy_oJDGA0{12UatB7Z{UdO6gZCJYgu(P(7L z^QRIU4u^gs(L`T;o4mD~*a!A7Li~hSO3-vVH4{D&6AzvfwTyYoGobf_!C*j@O2ujl zxoWlQ=g@s|#3q1nguG$u^}4?yV4`6Nn;dy{rU?Yr+U>T#2JuD%j||ECrQ7Yw^?(U& z1YNViUjKdnN#TAT}EZ_FP`Rqkg}?7o08i5l`|&VY0b9^16A~OVFRsE^Tkg z{d-v^7r{|yNzrhG4dB;|$79)2qh->NHkE{}4)a>ArsvU!nh>;HF3raYL9^K`ycP`> yo6qNwz00es{d)ru{1rNEYnHI?MZ~Rh2=NzrPN!N5SipP$0000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700KctL_t(&L+z8%ZNo4OM7vX`%`n}d`}EV5 z>pa4`k`X1hNf68MGn_MZBoD~jvMdt^SDgGqFy&ew4@c+f#TAdB13n6!s~1-s!3O%g zJOxJsa>Ws5FhVnsa^D57cVRRjSL|U16A%Vd?k?WTV`Zd@q+vh4rI03SNSN&`zw^Tg7G|7%t!Z9p%*hgOE4UW4uX9j4JP<2;Oi~3hq?&w%pU5>Rqv$&a3DGe_Wjj^ zi3~#n^wD4f@YOl~8k=AD`XvMdg$C$n4<@1+Oz@Xc@YDYPjqO1WyqFLSh92?q!9aFE zGi(LZ;u>75@5l1>L#s|{okK6zwm&aQWT2SgS%Lu$KxnAJToXv;ex+ z_3GtO!S`})d+{=g8O~s8%#dI-pdf%2`0kh6!}neypK{fEsnClV&tNt%G-C}0%6$a< zb)lo!+_D$AVg`(@ffve3xsQOqx7-nIZrKZ5@fJELzrhXX570hG(Y+K`yamQHm@60( zh{E@0LFdwaTDYQzHdOfjEa+UiPYYM{(1uatY+aVDU$!obD|%!bXX~Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700J#ZL_t(&L+z7MZo@DPLwl#4wujjbcAtG_ zN>dUxBf*xFuHEV&FGysXmI&msEX%~fElwT~j9u&F>1bbB+~N_m!Dpd;WpRrmu%WN( zOK>zGw>ZKKMra0N_pjicZHxxw7BkFX0>WVIPVr%nm9n-H)gz>j84Qp5%$oC)W^TzDCUhBKHA7}2m5ObZDPzTOazgfm$iL(t&P+XX{m zgIzE(XcqW-&98he-#1!1rL_owg2p|H!(EzBc zE?c)p1=bmaLP3D)-X@q{V6+Pc*nI@}m&27{Jxk%8K1bfWHm`u%0FI zv1^+PFQdSa#$Grl0Dm3mDC}G3(n1y(^8jb4Gl0K0+!5Hf&ZUJc+AzNV3OWyPiy3gh z3`W2K2u1_S>U!z*>(wc4F+=acy}Dj{{d#qZTg-q16x9LM0oC=QK44S_++qej2&fLI j4ydjd^#P;0jpgzKl_VABZB91?00000NkvXXu0mjfXXPhJ literal 0 HcmV?d00001 diff --git a/Properties/cat-11.png b/Properties/cat-11.png new file mode 100644 index 0000000000000000000000000000000000000000..2cbed25574e80f25011cb9624bfa010205c4b893 GIT binary patch literal 707 zcmeAS@N?(olHy`uVBq!ia0vp^Za}QX!3HGxBy^VoDaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(eheoCO|{#XzNpL74G){)!Z!pk#?_L`iUdT1k0gQ7S`0VrE{6US4X6f{C7i zo}rO5OZFQE1}0-q7srqc=eLtD<{mcSaoHTa{Hwv;1;4*sUYabgIM=gd${aJ_lpmZ% zuCgbYYof20CMB?`^BcI$%l%orW4%sV0ZZP8^$+%KnsHEW%fj_fH6=_>@O(4qRh-p! z;YlK85#?+nG}rT%$3pSW?Z$4x{5A_G8=7Q@t(W@| zv;X~;PcyFB9`ScGm{swBb8oNVJy*6$^<=iptH&!%`uAJbSxqYm9pPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700KctL_t(&L+z96alcm!VXUhrPM$l?gT(AW7P zI2w>Fjxd8Es)5+`FR0nZXh5>q!we=M492c5KJ1prYl)~HL3_+#0PXQO!Pr&MYwTLg zU;-XKn1evy=WtIHd=LzNbDfdJ9(=$Iro;zaIT+}k&*uW4C%!45U_hwHzPQkf3(sIm zTzE8?;I9B(HUfMuedkaxN+aPq?28M%xbUk7gT{!ce>j)`=qfWg=!;O_1$Wm z8Y80qmkp*Sg!d8EV1mC)1a#Fd*TnbVasYo1l|}GA;^l(@N+gH-tHIRRf`gu#c}v{K z45q|=a4-xSBX)uu%x1JO-vH|~pUd|zwLYfy9qM*g|9D`~7_k$4mtZ0w!0dt<1bVzU z$Y-|n@o%p4dpoO7JhTD<=Iw)FT(CEo;I9TfENr)e+{_W|R^UG13lF2X;0&h31rZDf z6a)|fy~qIS+}B&TTLIqNS$*MQ6c?PqtU$;GIT&Eq2++;V<>a$_h;;0f{OLUn{bxXdT?z*Gj8iYhNp}sK5Y* z`vCU=?rVj0z-V6$Syb>KzPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700L4;L_t(&L+z7Mj@%#+ME6cP?HndI$bItZ z1<$;esqJ=yJ;_>Yke`K{rl3ml$FeMMrHuOecJyAOxWzN*1-}cu*C=lB2zH@Ak4tbi zAh&pg8H|tyQl9^UcXr`uKyGn_8B9PJOnHX*RbG{CtrFEExx?(!48aqh7|-H( zV1SJgUxGOZdU|o#p4ro<-&)u8GM;{!WUw*fw!tU}nc!$J!LNoM7cQ@ct(h~FSA+S$ zACplKGQk;4iwP2p1{4GkfnH>Qy7rCM%d3I)GM>Je9K{4@FfAsi!9aPAfbQ1Z7S>0J zbjoObX<-x-_Cl$Y=LqPk5nz3kNT-a}mlk?KP;tXK0lI4FEOuYVTg5H*fUx43I|I6& zxii>(9d8x4xJPzT)~}BGQrzMm2z#MaSid^zOL2>PAe;r#+fQFcuTF1YhFjbtI~aQV z>C5QV>FvvKi+gZDL~p=I0?8QA U=`ig&`~Uy|07*qoM6N<$f|Z>^p8x;= literal 0 HcmV?d00001 diff --git a/Properties/cat-3.png b/Properties/cat-3.png new file mode 100644 index 0000000000000000000000000000000000000000..164c30343ad1c0df63473717ce5a69abe2dcc31f GIT binary patch literal 704 zcmV;x0zdtUP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700J;cL_t(&L+z8%ZNo4OM6*+;^)TI_`}EVL z;~XKK$%>SmHmzm&847hekq6|quIs7DvweOY?kkH~9Dy7BEx4~NX0Zot=wo>aMgua7 zJsgP zn3KRgdLQ<@;2Xi9?(TCTiyF^hYSj2i!N7l6mI}SsICIAd2EScD=&9fBFeyANPLs^i~W zKkQ2z-=e|?p3&ukVe}x84yN%}gD)DEPenP*2=b}WnWT*rdXTt$Fbs?|7!IfrfCqfN z0Z?aMwl1Fv>>mcr0;3rW0i#1OK;9AHUk+D-{Vbl3Jo{W&^rD7VC>!#Q0Dm3?U_XoJ zBhNk;7QMhwjjeD_0RBACDB3sA#f2;|)(ecG&H(<_a3g5nJQo+Dc#Wd|SKzsTS&RVV z84LkqFPN+@O4Ki^^Dv7Mq8CSXQKEiPorhVBfD0Jb0oDQ5MPVP%t7FV!1ilEc4zLce mE(-gAUL9i=Co>p__4WlIJQ}X@5q+}&0000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700J#ZL_t(&L+z7MZiFxlME6cTEr;n1dY^t$ zv+Rt>U^j7KyIT>&L*Ps%_DJ03c^){p#laJTv1@s}9PKNMTReg`_$suoEN-y}HuN>U z1xEvNi#?2BglZsm{|VmNMsGlFF~bNZAPmOt6d(3j*wz-JdW7^bf&tRU`vhZmL3XgS z7{LU*eK041b9f%sqTnmRpdPMkAq$O1FeNm8QZVqJrm2AEI(zN}!QeaegAOmf&~OA( zLc`u*qF>)R5rd>kaWtIFq%}1q~itG8l?~ zVHb=HngzaI^9!Hz_nn$fsqI637lJ}zg-Zm(MQ5V|-7c7e)Xv9xt4_bQzFW68!cZ1; zqXOM!g3%3t8-;W*m0u0MIM*Hp+n$bKj{?qQZH!K(UF%$U>4k<~C<}HU0scA&z?aElrAAfP&+ jI-t5%)CctHR_5Cek2)FR(L@O(4qRh-p! z;YlK85#?+nG}rT%$3pSW?Z$4x{5A_G8=7Q@t(W@| zv;X~;PcyFB9`ScGm{swBb8oNVJy*6$^<=iptH&!%`uAJbSxqYm9pPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700KctL_t(&L+z96alcm!VXUhrPM$l?gT(AW7P zI2w>Fjxd8Es)5+`FR0nZXh5>q!we=M492c5KJ1prYl)~HL3_+#0PXQO!Pr&MYwTLg zU;-XKn1evy=WtIHd=LzNbDfdJ9(=$Iro;zaIT+}k&*uW4C%!45U_hwHzPQkf3(sIm zTzE8?;I9B(HUfMuedkaxN+aPq?28M%xbUk7gT{!ce>j)`=qfWg=!;O_1$Wm z8Y80qmkp*Sg!d8EV1mC)1a#Fd*TnbVasYo1l|}GA;^l(@N+gH-tHIRRf`gu#c}v{K z45q|=a4-xSBX)uu%x1JO-vH|~pUd|zwLYfy9qM*g|9D`~7_k$4mtZ0w!0dt<1bVzU z$Y-|n@o%p4dpoO7JhTD<=Iw)FT(CEo;I9TfENr)e+{_W|R^UG13lF2X;0&h31rZDf z6a)|fy~qIS+}B&TTLIqNS$*MQ6c?PqtU$;GIT&Eq2++;V<>a$_h;;0f{OLUn{bxXdT?z*Gj8iYhNp}sK5Y* z`vCU=?rVj0z-V6$Syb>KzPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700KctL_t(&L+z8%ZNo4OM7vX`%`n}d`}EV5 z>pa4`k`X1hNf68MGn_MZBoD~jvMdt^SDgGqFy&ew4@c+f#TAdB13n6!s~1-s!3O%g zJOxJsa>Ws5FhVnsa^D57cVRRjSL|U16A%Vd?k?WTV`Zd@q+vh4rI03SNSN&`zw^Tg7G|7%t!Z9p%*hgOE4UW4uX9j4JP<2;Oi~3hq?&w%pU5>Rqv$&a3DGe_Wjj^ zi3~#n^wD4f@YOl~8k=AD`XvMdg$C$n4<@1+Oz@Xc@YDYPjqO1WyqFLSh92?q!9aFE zGi(LZ;u>75@5l1>L#s|{okK6zwm&aQWT2SgS%Lu$KxnAJToXv;ex+ z_3GtO!S`})d+{=g8O~s8%#dI-pdf%2`0kh6!}neypK{fEsnClV&tNt%G-C}0%6$a< zb)lo!+_D$AVg`(@ffve3xsQOqx7-nIZrKZ5@fJELzrhXX570hG(Y+K`yamQHm@60( zh{E@0LFdwaTDYQzHdOfjEa+UiPYYM{(1uatY+aVDU$!obD|%!bXX~Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700K-&L_t(&L+z7Ma-%R1M02N{)`!^}?0xb{ zgzYzl+S5n^?}iGfcoZIudemLzk7=6TN?G&k$I*L@k{0)%7yK#oUZbSNEyRWX-VVXu zfYRa?u3&^_Am#ZlWU~uf14@e{T)`M@Fy$HIv%F@OwHa4;$UUxLK<;szV9HZ5YV2KH z!59u7%udjcIojibH^J~*>q=T2AqHH*)Wm={2ZQeIcB_ba$(afX2CSa?(!wYq{M&<} z(c$_x2V?#U=&BKjxpFR$U^0!F&{1D6g=eF~^`9mfCji>NIT!=FdJly@M|NfjeP!MK zK=tEZFoM)U`%e=LI3cAOjQPtr(AD|xCho5}ycC{|;DnT49}I8+oFFuVY4HpWJ)QTj z2_N|9L!l9z5S%6$-~d=b4d!XIGT(ul1AnHkJFPyYb&h&j58r$^H~?1gY{38r9WTLb z1U2Iy;epwH{e6--8*ZC!epwG+d~78IuV89IP=kT;YysV+xh3q6;&jTYeQ9Bo z5Y9rGDbE(rRU^RuC{CxW+Lso3K~PD<*#Ww0XfJW!#M?|->;YjVGq(qHGjn^0`zGFI z(&8Gei?V-o)R&SL*FZQ6WrqEuqrQ~1xCX+lKzjS>YtgII+t(s3uF)C{z5VpH=+)`% zYmpY$V1T2yU;BDd9ccCGev7oYh7MGE`?aqZ)qz%@?zc#b7gsQb>Ej!`O3v#GY7V#n O0000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^Ag1Z(U700J;cL_t(&L+z8%ZNo4OM6*+;^)TI_`}EVL z;~XKK$%>SmHmzm&847hekq6|quIs7DvweOY?kkH~9Dy7BEx4~NX0Zot=wo>aMgua7 zJsgP zn3KRgdLQ<@;2Xi9?(TCTiyF^hYSj2i!N7l6mI}SsICIAd2EScD=&9fBFeyANPLs^i~W zKkQ2z-=e|?p3&ukVe}x84yN%}gD)DEPenP*2=b}WnWT*rdXTt$Fbs?|7!IfrfCqfN z0Z?aMwl1Fv>>mcr0;3rW0i#1OK;9AHUk+D-{Vbl3Jo{W&^rD7VC>!#Q0Dm3?U_XoJ zBhNk;7QMhwjjeD_0RBACDB3sA#f2;|)(ecG&H(<_a3g5nJQo+Dc#Wd|SKzsTS&RVV z84LkqFPN+@O4Ki^^Dv7Mq8CSXQKEiPorhVBfD0Jb0oDQ5MPVP%t7FV!1ilEc4zLce mE(-gAUL9i=Co>p__4WlIJQ}X@5q+}&0000Oic*!2~2*L${m(QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKtah8*NBqf{Irtt#G+J&g2c?c61}|C5(N`I z13g0{XO`?YKt&0jE{-7)?r+a76g=P{aM#se0w4V9o86m}`kq62)$mX!$ zK3KLSoe@ZaITOne%$H2tn%^c^-{9M(Y#9qzY!iIHe(ziB>o2*@`;F}NY{EYR?PBnB L^>bP0l+XkKM2l1H literal 0 HcmV?d00001 diff --git a/moduleManager.cs b/moduleManager.cs index 56845a08..2ac4fff6 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -7,7 +7,9 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using TMPro; using UnityEngine; +using UnityEngine.UI; using Debug = UnityEngine.Debug; namespace ModuleManager @@ -27,8 +29,13 @@ public class ModuleManager : MonoBehaviour private string version = ""; - private Texture2D tex; - private Texture2D tex2; + //private Texture2D tex; + //private Texture2D tex2; + + private Sprite[] catFrames; + private Texture2D rainbow; + + private int activePos = 0; private bool nyan = false; @@ -103,18 +110,118 @@ internal void Awake() log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); list.Insert(1, loader); } + + nyan = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1) + || (DateTime.Now < new DateTime(2016, 11, 1)) + || Environment.GetCommandLineArgs().Contains("-nyan-nyan"); - tex = new Texture2D(33, 20, TextureFormat.ARGB32, false); - tex.LoadImage(Properties.Resources.cat); - Color[] pix = tex.GetPixels(0, 0, 1, tex.height); - tex2 = new Texture2D(1, 20, TextureFormat.ARGB32, false); - tex2.SetPixels(pix); - tex2.Apply(); + loadedInScene = true; + } - nyan = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1) || - Environment.GetCommandLineArgs().Contains("-nyan-nyan"); + private TextMeshProUGUI status; + private TextMeshProUGUI errors; + private TextMeshProUGUI warning; - loadedInScene = true; + + private void Start() + { + SendCatToLaunchBay(); + + Canvas canvas = LoadingScreen.Instance.GetComponentInChildren(); + + status = CreateTextObject(canvas, "MMStatus"); + errors = CreateTextObject(canvas, "MMErrors"); + warning = CreateTextObject(canvas, "MMWarning"); + warning.text = ""; + + //if (Versioning.version_major == 1 && Versioning.version_minor == 0 && Versioning.Revision == 5 && Versioning.BuildID == 1024) + //{ + // warning.text = "Your KSP 1.0.5 is running on build 1024. You should upgrade to build 1028 to avoid problems with addons."; + // //if (GUI.Button(new Rect(Screen.width / 2f - 100, offsetY, 200, 20), "Click to open the Forum thread")) + // // Application.OpenURL("http://forum.kerbalspaceprogram.com/index.php?/topic/124998-silent-patch-for-ksp-105-published/"); + //} + } + + private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) + { + GameObject statusGameObject = new GameObject(name); + TextMeshProUGUI text = statusGameObject.AddComponent(); + text.text = "STATUS"; + text.fontSize = 16; + text.autoSizeTextContainer = true; + text.font = Resources.Load("Fonts/Calibri SDF", typeof(TMP_FontAsset)) as TMP_FontAsset; + text.alignment = TextAlignmentOptions.Center; + text.enableWordWrapping = false; + text.isOverlay = true; + statusGameObject.transform.SetParent(canvas.transform); + + return text; + } + + private void SendCatToLaunchBay() + { + if (!nyan) + return; + + // Nyancat are GO !!! + + Texture2D[] tex = new Texture2D[12]; + for (int i = 0; i < tex.Length; i++) + { + tex[i] = new Texture2D(70, 42, TextureFormat.ARGB32, false); + } + tex[0].LoadImage(Properties.Resources.cat1); + tex[1].LoadImage(Properties.Resources.cat2); + tex[2].LoadImage(Properties.Resources.cat3); + tex[3].LoadImage(Properties.Resources.cat4); + tex[4].LoadImage(Properties.Resources.cat5); + tex[5].LoadImage(Properties.Resources.cat6); + tex[6].LoadImage(Properties.Resources.cat7); + tex[7].LoadImage(Properties.Resources.cat8); + tex[8].LoadImage(Properties.Resources.cat9); + tex[9].LoadImage(Properties.Resources.cat10); + tex[10].LoadImage(Properties.Resources.cat11); + tex[11].LoadImage(Properties.Resources.cat12); + + rainbow = new Texture2D(39, 36, TextureFormat.ARGB32, false); + rainbow.LoadImage(Properties.Resources.rainbow); + rainbow.Apply(); + + catFrames = new Sprite[12]; + + for (int i = 0; i < tex.Length; i++) + { + tex[i].Apply(); + catFrames[i] = Sprite.Create(tex[i], new Rect(0, 0, tex[i].width, tex[i].height), new Vector2(.5f, .5f)); + catFrames[i].name = "cat" + i; + } + + GameObject cat = new GameObject("NyanCat"); + SpriteRenderer sr = cat.AddComponent(); + TrailRenderer trail = cat.AddComponent(); + CatMover catMover = cat.AddComponent(); + + sr.sprite = catFrames[0]; + + trail.material = new Material(Shader.Find("Particles/Alpha Blended")); + Debug.Log("material = " + trail.material); + trail.material.mainTexture = rainbow; + trail.time = 1.5f; + trail.startWidth = rainbow.height; + trail.endWidth = rainbow.height * 0.9f; + + cat.layer = LayerMask.NameToLayer("UI"); + + catMover.frames = catFrames; + int scale = 70; + + scale *= 1; + if (Screen.height >= 1080) + scale *= 2; + if (Screen.height > 1440) + scale *= 3; + + cat.transform.localScale = scale * Vector3.one; } // Unsubscribe from events when the behavior dies @@ -126,8 +233,41 @@ internal void OnDestroy() internal void Update() { - if (GameSettings.MODIFIER_KEY.GetKey() && Input.GetKeyDown(KeyCode.F11)) - showUI = !showUI; + if (GameSettings.MODIFIER_KEY.GetKey() && Input.GetKeyDown(KeyCode.F11) + && (HighLogic.LoadedScene == GameScenes.SPACECENTER || HighLogic.LoadedScene == GameScenes.MAINMENU) + && !inRnDCenter) + { + PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + new MultiOptionDialog("", + "ModuleManager", + HighLogic.UISkin, + new Rect(0.5f, 0.5f, 150f, 60f), + new DialogGUIFlexibleSpace(), + new DialogGUIVerticalLayout( + new DialogGUIFlexibleSpace(), + new DialogGUIButton("Reload Database", + delegate + { + MMPatchLoader.keepPartDB = false; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Quick Reload Database", + delegate + { + MMPatchLoader.keepPartDB = true; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Dump Database to Files", + delegate + { + StartCoroutine(DataBaseReloadWithMM(true)); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Close",() => {}, 140.0f, 30.0f, true) + )), + false, + HighLogic.UISkin); + } if (totalTime.IsRunning && HighLogic.LoadedScene == GameScenes.MAINMENU) { @@ -135,9 +275,34 @@ internal void Update() log("Total loading Time = " + ((float)totalTime.ElapsedMilliseconds / 1000).ToString("F3") + "s"); Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND; + } + float offsetY = Mathf.FloorToInt(0.3f * Screen.height); + float h; + if (warning) + { + warning.transform.localPosition = new Vector3(0, -offsetY); + h = warning.textBounds.size.y; + if (h > 0) + offsetY = offsetY + h + 10; } + if (status) + { + status.transform.localPosition = new Vector3(0, -offsetY); + status.text = MMPatchLoader.Instance.status; + + h = status.textBounds.size.y; + if (h > 0) + offsetY = offsetY + h + 10; + } + + if (errors) + { + errors.transform.localPosition = new Vector3(0, -offsetY); + errors.text = MMPatchLoader.Instance.errors; + } + if (reloading) { float percent = 0; @@ -155,119 +320,6 @@ internal void Update() } #region GUI stuff. - - public void OnGUI() - { - if (HighLogic.LoadedScene == GameScenes.LOADING && MMPatchLoader.Instance != null) - { - float offsetY = Mathf.FloorToInt(0.8f * Screen.height); - - if (Versioning.version_major == 1 && Versioning.version_minor == 0 && Versioning.Revision == 5 && Versioning.BuildID == 1024) - { - GUIStyle centeredWarningStyle = new GUIStyle(GUI.skin.GetStyle("Label")) - { - alignment = TextAnchor.UpperCenter, - fontSize = 16, - normal = { textColor = Color.yellow } - }; - const string warning = "Your KSP 1.0.5 is running on build 1024. You should upgrade to build 1028 to avoid problems with addons."; - - Vector2 sizeOfWarningLabel = centeredWarningStyle.CalcSize(new GUIContent(warning)); - - GUI.Label(new Rect(Screen.width / 2f - (sizeOfWarningLabel.x / 2f), offsetY, sizeOfWarningLabel.x, sizeOfWarningLabel.y), warning, centeredWarningStyle); - - offsetY += sizeOfWarningLabel.y; - if (GUI.Button(new Rect(Screen.width/2f - 100, offsetY, 200, 20), "Click to open the Forum thread")) - Application.OpenURL("http://forum.kerbalspaceprogram.com/index.php?/topic/124998-silent-patch-for-ksp-105-published/"); - - offsetY += 25; - } - - //if (IsABadIdea()) - //{ - // GUIStyle centeredWarningStyle = new GUIStyle(GUI.skin.GetStyle("Label")) - // { - // alignment = TextAnchor.UpperCenter, - // fontSize = 16, - // normal = { textColor = Color.yellow } - // }; - // const string warning = "You are using 64-bit KSP on Windows. This version of KSP is known to cause crashes unrelated to mods."; - // Vector2 sizeOfWarningLabel = centeredWarningStyle.CalcSize(new GUIContent(warning)); - // - // GUI.Label(new Rect(Screen.width / 2f - (sizeOfWarningLabel.x / 2f), offsetY, sizeOfWarningLabel.x, sizeOfWarningLabel.y), warning, centeredWarningStyle); - // offsetY += sizeOfWarningLabel.y; - //} - - GUIStyle centeredStyle = new GUIStyle(GUI.skin.GetStyle("Label")) - { - alignment = TextAnchor.UpperCenter, - fontSize = 16 - }; - Vector2 sizeOfLabel = centeredStyle.CalcSize(new GUIContent(MMPatchLoader.Instance.status)); - GUI.Label(new Rect(Screen.width / 2f - (sizeOfLabel.x / 2f), offsetY, sizeOfLabel.x, sizeOfLabel.y), MMPatchLoader.Instance.status, centeredStyle); - offsetY += sizeOfLabel.y; - - if (MMPatchLoader.Instance.errorCount > 0) - { - GUIStyle errorStyle = new GUIStyle(GUI.skin.GetStyle("Label")) - { - alignment = TextAnchor.UpperLeft, - fontSize = 16 - }; - Vector2 sizeOfError = errorStyle.CalcSize(new GUIContent(MMPatchLoader.Instance.errors)); - GUI.Label(new Rect(Screen.width / 2f - (sizeOfLabel.x / 2), offsetY, sizeOfError.x, sizeOfError.y), MMPatchLoader.Instance.errors, errorStyle); - offsetY += sizeOfError.y; - } - - - if (nyan) - { - GUI.color = Color.white; - int scale = 1; - if (Screen.height >= 1080) - scale = 2; - if (Screen.height > 1440) - scale = 3; - - int trailLength = 8 * tex.width * scale; - int totalLenth = trailLength + tex.width * scale; - int startPos = activePos - totalLenth; - - Color guiColor = Color.white; - int currentOffset = 0; - int heightOffset = 0; - while (currentOffset < trailLength) - { - guiColor.a = (float)currentOffset / trailLength; - GUI.color = guiColor; - - heightOffset = Mathf.RoundToInt(1f + Mathf.Sin(2f * Mathf.PI * (startPos + currentOffset) / (Screen.width / 6f)) * (tex.height * scale / 6f)); - - GUI.DrawTexture(new Rect(startPos + currentOffset, heightOffset + offsetY, tex2.width, tex2.height * scale), tex2); - currentOffset++; - } - GUI.DrawTexture(new Rect(startPos + currentOffset, heightOffset + offsetY, tex.width * scale, tex.height * scale), tex); - - activePos = (activePos + 3) % (Screen.width + totalLenth); - GUI.color = Color.white; - } - } - - - if (showUI && - (HighLogic.LoadedScene == GameScenes.SPACECENTER || HighLogic.LoadedScene == GameScenes.MAINMENU) && - !inRnDCenter) - { - windowPos = GUILayout.Window( - GetType().FullName.GetHashCode(), - windowPos, - WindowGUI, - "ModuleManager " + version, - GUILayout.Width(200), - GUILayout.Height(20)); - } - } - internal static IntPtr intPtr = new IntPtr(long.MaxValue); /* Not required anymore. At least @@ -277,26 +329,6 @@ public static bool IsABadIdea() } */ - private void WindowGUI(int windowID) - { - GUILayout.BeginVertical(); - - if (GUILayout.Button("Reload Database")) - { - MMPatchLoader.keepPartDB = false; - StartCoroutine(DataBaseReloadWithMM()); - } - if (GUILayout.Button("Quick Reload Database")) - { - MMPatchLoader.keepPartDB = true; - StartCoroutine(DataBaseReloadWithMM()); - } - if (GUILayout.Button("Dump Database to File")) - StartCoroutine(DataBaseReloadWithMM(true)); - GUILayout.EndVertical(); - GUI.DragWindow(); - } - private IEnumerator DataBaseReloadWithMM(bool dump = false) { reloading = true; @@ -469,20 +501,18 @@ public class MMPatchLoader : LoadingSystem private static ConfigNode topNode; - private static string cachePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "GameData" - + Path.DirectorySeparatorChar + "ModuleManager.ConfigCache"; + private static string cachePath; - internal static readonly string techTreeFile = "GameData" + Path.DirectorySeparatorChar + "ModuleManager.TechTree"; - internal static readonly string techTreePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + techTreeFile; + internal static string techTreeFile; + internal static string techTreePath; - internal static readonly string physicsFile = "GameData" + Path.DirectorySeparatorChar + "ModuleManager.Physics"; - internal static readonly string physicsPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + physicsFile; - private static readonly string defaultPhysicsPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "Physics.cfg"; + internal static string physicsFile; + internal static string physicsPath; + private static string defaultPhysicsPath; - internal static readonly string partDatabasePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "PartDatabase.cfg"; + internal static string partDatabasePath; - private static readonly string shaPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "GameData" - + Path.DirectorySeparatorChar + "ModuleManager.ConfigSHA"; + private static string shaPath; private UrlDir.UrlFile physicsUrlFile; @@ -493,8 +523,7 @@ public class MMPatchLoader : LoadingSystem private readonly Stopwatch patchSw = new Stopwatch(); - private static readonly List postPatchCallbacks = - new List(); + private static readonly List postPatchCallbacks = new List(); private const float yieldInterval = 1f/30f; // Patch at ~30fps @@ -509,12 +538,22 @@ private void Awake() } Instance = this; DontDestroyOnLoad(gameObject); + + cachePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "GameData" + Path.DirectorySeparatorChar + "ModuleManager.ConfigCache"; + techTreeFile = "GameData" + Path.DirectorySeparatorChar + "ModuleManager.TechTree"; + techTreePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + techTreeFile; + physicsFile = "GameData" + Path.DirectorySeparatorChar + "ModuleManager.Physics"; + physicsPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + physicsFile; + defaultPhysicsPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "Physics.cfg"; + partDatabasePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "PartDatabase.cfg"; + shaPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "GameData" + Path.DirectorySeparatorChar + "ModuleManager.ConfigSHA"; } private bool ready; public override bool IsReady() { + //return false; if (ready) { patchSw.Stop(); @@ -2743,6 +2782,7 @@ public static List SplitConstraints(string condition) public static bool CheckConstraints(ConfigNode node, string constraints) { constraints = RemoveWS(constraints); + if (constraints.Length == 0) return true; @@ -2785,15 +2825,23 @@ public static bool CheckConstraints(ConfigNode node, string constraints) last = subNode; } if (last != null) + { + print("CheckConstraints: " + constraints + " " + (not ^ any)); return not ^ any; - + } + print("CheckConstraints: " + constraints + " " + (not ^ false)); return not ^ false; case '#': // #module[Winglet] if (node.HasValue(type) && WildcardMatchValues(node, type, name)) - return CheckConstraints(node, remainingConstraints); + { + bool ret2 = CheckConstraints(node, remainingConstraints); + print("CheckConstraints: " + constraints + " " + ret2); + return ret2; + } + print("CheckConstraints: " + constraints + " false"); return false; case '~': @@ -2801,16 +2849,27 @@ public static bool CheckConstraints(ConfigNode node, string constraints) // ~breakingForce[] breakingForce is not present // or: ~breakingForce[100] will be true if it's present but not 100, too. if (name == "" && node.HasValue(type)) + { + print("CheckConstraints: " + constraints + " false"); return false; + } if (name != "" && WildcardMatchValues(node, type, name)) + { + print("CheckConstraints: " + constraints + " false"); return false; - return CheckConstraints(node, remainingConstraints); + } + bool ret = CheckConstraints(node, remainingConstraints); + print("CheckConstraints: " + constraints + " " + ret); + return ret; default: + print("CheckConstraints: " + constraints + " false"); return false; } } - return constraintList.TrueForAll(c => CheckConstraints(node, c)); + bool ret3 = constraintList.TrueForAll(c => CheckConstraints(node, c)); + print("CheckConstraints: " + constraints + " " + ret3); + return ret3; } public static bool WildcardMatchValues(ConfigNode node, string type, string value) diff --git a/packages.config b/packages.config new file mode 100644 index 00000000..a9f29a32 --- /dev/null +++ b/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From ff943850e51f0ed65dfb25664da9f2bb61ac98ce Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 5 Oct 2016 22:34:21 +0200 Subject: [PATCH 003/342] v2.7.0 --- Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 8896a384..d59acafe 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.6.25")] +[assembly: AssemblyVersion("2.7.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 517280bd676d5be1958f83bf63e2bd5fbca0552e Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 12:06:41 +0200 Subject: [PATCH 004/342] Fix the problem with setting value name that include comma (unless the comma is followed by a number) --- moduleManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moduleManager.cs b/moduleManager.cs index 2ac4fff6..32a8f769 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -1657,7 +1657,7 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) } // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*]*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s([+\-*/^!]))?"); + private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*]+(?:,\D[\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s([+\-*/^!]))?"); // Path is group 1, operator is group 5 private static Regex parseAssign = new Regex(@"(.*)(?:\s)+([+\-*/^!])?"); From aef5577a32a44dcb3702af82d931807e8307c3fd Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 12:12:40 +0200 Subject: [PATCH 005/342] Dispaly how many exception were encountered --- moduleManager.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 32a8f769..8035a60b 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -353,7 +353,7 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) log("DB Reload OK with patchCount=" + MMPatchLoader.Instance.patchedNodeCount + " errorCount=" + MMPatchLoader.Instance.errorCount + " needsUnsatisfiedCount=" + - MMPatchLoader.Instance.needsUnsatisfiedCount); + MMPatchLoader.Instance.needsUnsatisfiedCount + " exceptionCount=" + MMPatchLoader.Instance.exceptionCount); PartResourceLibrary.Instance.LoadDefinitions(); @@ -479,6 +479,8 @@ public class MMPatchLoader : LoadingSystem public int errorCount = 0; + public int exceptionCount = 0; + public int needsUnsatisfiedCount = 0; private int catEatenCount = 0; @@ -707,6 +709,7 @@ private List PrePatchInit() { log("Skipping :FOR init for line " + name + ". The line most likely contain a space that should be removed"); + errorCount++; } } } @@ -1292,7 +1295,11 @@ private void StatusUpdate() status = "ModuleManager: " + patchedNodeCount + " patch" + (patchedNodeCount != 1 ? "es" : "") + (useCache ? " loaded from cache" : " applied"); if (errorCount > 0) - status += ", found " + errorCount + " error" + (errorCount != 1 ? "s" : ""); + status += ", found " + errorCount + " error" + (errorCount != 1 ? "s" : "") + ""; + + if (exceptionCount > 0) + status += ", encountered " + exceptionCount + " exception" + (exceptionCount != 1 ? "s" : "") + ""; + if (catEatenCount > 0) status += ", " + catEatenCount + " patch" + (catEatenCount != 1 ? "es were" : " was") + " eaten by the Win64 cat"; } @@ -1351,6 +1358,7 @@ private void CheckNeeds(List excludePaths) { log("Exception while checking needs : " + currentMod.url + " with a type of " + currentMod.type + "\n" + ex); log("Node is : " + PrettyConfig(currentMod)); + exceptionCount++; } } } @@ -1640,7 +1648,8 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) catch (Exception e) { log("Exception while processing node : " + mod.url + "\n" + e); - log(PrettyConfig(mod)); + exceptionCount++; + log("Processed node was\n" + PrettyConfig(mod)); mod.parent.configs.Remove(mod); } finally From 2f26781f2505949ecc66aad3fa96b39999a60a9e Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 12:13:32 +0200 Subject: [PATCH 006/342] Remove some debug spam --- moduleManager.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 8035a60b..f0722f60 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -2835,10 +2835,10 @@ public static bool CheckConstraints(ConfigNode node, string constraints) } if (last != null) { - print("CheckConstraints: " + constraints + " " + (not ^ any)); + //print("CheckConstraints: " + constraints + " " + (not ^ any)); return not ^ any; } - print("CheckConstraints: " + constraints + " " + (not ^ false)); + //print("CheckConstraints: " + constraints + " " + (not ^ false)); return not ^ false; case '#': @@ -2847,10 +2847,10 @@ public static bool CheckConstraints(ConfigNode node, string constraints) if (node.HasValue(type) && WildcardMatchValues(node, type, name)) { bool ret2 = CheckConstraints(node, remainingConstraints); - print("CheckConstraints: " + constraints + " " + ret2); + //print("CheckConstraints: " + constraints + " " + ret2); return ret2; } - print("CheckConstraints: " + constraints + " false"); + //print("CheckConstraints: " + constraints + " false"); return false; case '~': @@ -2859,25 +2859,25 @@ public static bool CheckConstraints(ConfigNode node, string constraints) // or: ~breakingForce[100] will be true if it's present but not 100, too. if (name == "" && node.HasValue(type)) { - print("CheckConstraints: " + constraints + " false"); + //print("CheckConstraints: " + constraints + " false"); return false; } if (name != "" && WildcardMatchValues(node, type, name)) { - print("CheckConstraints: " + constraints + " false"); + //print("CheckConstraints: " + constraints + " false"); return false; } bool ret = CheckConstraints(node, remainingConstraints); - print("CheckConstraints: " + constraints + " " + ret); + //print("CheckConstraints: " + constraints + " " + ret); return ret; default: - print("CheckConstraints: " + constraints + " false"); + //print("CheckConstraints: " + constraints + " false"); return false; } } bool ret3 = constraintList.TrueForAll(c => CheckConstraints(node, c)); - print("CheckConstraints: " + constraints + " " + ret3); + //print("CheckConstraints: " + constraints + " " + ret3); return ret3; } From 33041dc2c0ef4a48f70d88d94a42f344f2e4229f Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 12:14:46 +0200 Subject: [PATCH 007/342] Lower garbage by removing implicit allocation in CheckConstraints --- moduleManager.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index f0722f60..82c46835 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -9,7 +9,6 @@ using System.Text.RegularExpressions; using TMPro; using UnityEngine; -using UnityEngine.UI; using Debug = UnityEngine.Debug; namespace ModuleManager @@ -2788,6 +2787,8 @@ public static List SplitConstraints(string condition) return conditions; } + static readonly char[] contraintSeparators = { '[', ']' }; + public static bool CheckConstraints(ConfigNode node, string constraints) { constraints = RemoveWS(constraints); @@ -2809,8 +2810,7 @@ public static bool CheckConstraints(ConfigNode node, string constraints) constraints = constraints.Substring(0, start - 5); } - char[] sep = { '[', ']' }; - string[] splits = constraints.Split(sep, 3); + string[] splits = constraints.Split(contraintSeparators, 3); string type = splits[0].Substring(1); string name = splits.Length > 1 ? splits[1] : null; @@ -2876,7 +2876,12 @@ public static bool CheckConstraints(ConfigNode node, string constraints) return false; } } - bool ret3 = constraintList.TrueForAll(c => CheckConstraints(node, c)); + + bool ret3 = true; + foreach (string constraint in constraintList) + { + ret3 = ret3 && CheckConstraints(node, constraint); + } //print("CheckConstraints: " + constraints + " " + ret3); return ret3; } From e7e1a7908a65aced855d40bee5bc2521a8474a54 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 13:58:44 +0200 Subject: [PATCH 008/342] Disable some warning that I am getting tired of seeing --- moduleManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/moduleManager.cs b/moduleManager.cs index 82c46835..4d19b422 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -468,6 +469,8 @@ orderby ass.GetName().Version descending, a.path ascending public delegate void ModuleManagerPostPatchCallback(); + [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] + [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public class MMPatchLoader : LoadingSystem { public int totalPatchCount = 0; From 8cd44cf3ffb296bb884172d95beafd534c2e6c9e Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 14:01:57 +0200 Subject: [PATCH 009/342] Prevent garbage generated by debug string that we do not display or print --- moduleManager.cs | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 4d19b422..500674b8 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -1686,11 +1686,14 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) #region Values + #if LOGSPAM string vals = "[ModuleManager] modding values"; + #endif foreach (ConfigNode.Value modVal in mod.values) { + #if LOGSPAM vals += "\n " + modVal.name + "= " + modVal.value; - + #endif string valName; Command cmd = ParseCommand(modVal.name, out valName); @@ -1873,8 +1876,10 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) if (value != null) { + #if LOGSPAM if (origVal.value != value) vals += ": " + origVal.value + " -> " + value; + #endif if (cmd != Command.Copy) origVal.value = value; @@ -1971,7 +1976,9 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) break; } } - //log(vals); + #if LOGSPAM + log(vals); + #endif #endregion Values @@ -2051,8 +2058,9 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) string tag = ""; string nodeType, nodeName; int index = 0; + #if LOGSPAM string msg = ""; - + #endif List subNodes = new List(); // three ways to specify: @@ -2090,9 +2098,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) if (tag == "*" || constraints.Length > 0) { // get ALL nodes - if (command == Command.Replace) - msg += " cannot wildcard a % node: " + subMod.name + "\n"; - else + if (command != Command.Replace) { ConfigNode n, last = null; while (true) @@ -2105,6 +2111,10 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) last = n; } } +#if LOGSPAM + else + msg += " cannot wildcard a % node: " + subMod.name + "\n"; +#endif } else { @@ -2119,7 +2129,9 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) // if the original exists modify it if (subNodes.Count > 0) { + #if LOGSPAM msg += " Applying subnode " + subMod.name + "\n"; + #endif ConfigNode newSubNode = ModifyNode(subNodes[0], subMod); subNodes[0].ClearData(); newSubNode.CopyTo(subNodes[0], newSubNode.name); @@ -2127,7 +2139,9 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) else { // if not add the mod node without the % in its name + #if LOGSPAM msg += " Adding subnode " + subMod.name + "\n"; + #endif ConfigNode copy = new ConfigNode(nodeType); @@ -2142,7 +2156,9 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) { if (subNodes.Count == 0) { + #if LOGSPAM msg += " Adding subnode " + subMod.name + "\n"; + #endif ConfigNode copy = new ConfigNode(nodeType); @@ -2156,12 +2172,16 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) else { // find each original subnode to modify, modify it and add the modified. + #if LOGSPAM if (subNodes.Count == 0) // no nodes to modify! msg += " Could not find node(s) to modify: " + subMod.name + "\n"; + #endif foreach (ConfigNode subNode in subNodes) { + #if LOGSPAM msg += " Applying subnode " + subMod.name + "\n"; + #endif ConfigNode newSubNode; switch (command) { @@ -2188,8 +2208,9 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) } } } - - //print(msg); + #if LOGSPAM + print(msg); + #endif } } From cb2f5d8efe6fc48e650840e09b9b6764c8380437 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 14:03:45 +0200 Subject: [PATCH 010/342] Prevent cache genration when there are exception and display the files that generated them --- moduleManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 500674b8..96c79984 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -813,14 +813,13 @@ private IEnumerator ProcessPatch(bool blocking) // :Final node yield return StartCoroutine(ApplyPatch(excludePaths, ":FINAL"), blocking); - PurgeUnused(excludePaths); #endregion Applying patches #region Logging - if (errorCount > 0) + if (errorCount > 0 || exceptionCount > 0) { foreach (string file in errorFiles.Keys) { @@ -1524,6 +1523,7 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) { UrlDir.UrlConfig mod = allConfigs[modsIndex]; int lastErrorCount = errorCount; + int lastExceptionCount = exceptionCount; try { string name = RemoveWS(mod.type); @@ -1656,8 +1656,8 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) } finally { - if (lastErrorCount < errorCount) - addErrorFiles(mod.parent, errorCount - lastErrorCount); + if (lastErrorCount < errorCount || lastExceptionCount < exceptionCount) + addErrorFiles(mod.parent, errorCount - lastErrorCount + exceptionCount - lastExceptionCount); } if (nextYield < Time.realtimeSinceStartup) { From d21da622d1163e9615b8840f0215385c797dd317 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 14:04:28 +0200 Subject: [PATCH 011/342] Improved feedback on what is going on --- moduleManager.cs | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 96c79984..4a24fe99 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -753,6 +753,10 @@ Coroutine StartCoroutine(IEnumerator enumerator, bool blocking) private IEnumerator ProcessPatch(bool blocking) { + status = "Checking Cache"; + log(status); + yield return null; + try { IsCacheUpToDate(); @@ -766,15 +770,17 @@ private IEnumerator ProcessPatch(bool blocking) #if DEBUG //useCache = false; #endif + if (!useCache) + { + status = "Pre patch init"; + log(status); + yield return null; - yield return null; + List excludePaths = PrePatchInit(); - List excludePaths = PrePatchInit(); + yield return null; - yield return null; - if (!useCache) - { // If we don't use the cache then it is best to clean the PartDatabase.cfg if (!keepPartDB && File.Exists(partDatabasePath)) File.Delete(partDatabasePath); @@ -783,18 +789,22 @@ private IEnumerator ProcessPatch(bool blocking) #region Check Needs - // Do filtering with NEEDS - log("Checking NEEDS."); + + // Do filtering with NEEDS + status = "Checking NEEDS."; + log(status); + yield return null; CheckNeeds(excludePaths); #endregion Check Needs - yield return null; - #region Applying patches - log("Applying patches"); + status = "Applying patches"; + log(status); + + yield return null; // :First node yield return StartCoroutine(ApplyPatch(excludePaths, ":FIRST"), blocking); @@ -842,6 +852,9 @@ private IEnumerator ProcessPatch(bool blocking) } else { + status = "Saving Cache"; + log(status); + yield return null; CreateCache(); } @@ -850,7 +863,9 @@ private IEnumerator ProcessPatch(bool blocking) } else { - log("Loading from Cache"); + status = "Loading from Cache"; + log(status); + yield return null; LoadCache(); } @@ -1511,7 +1526,9 @@ private void PurgeUnused(List excludePaths) // Apply patch to all relevent nodes public IEnumerator ApplyPatch(List excludePaths, string Stage) { + StatusUpdate(); log(Stage + (Stage == ":LEGACY" ? " (default) pass" : " pass")); + yield return null; activity = "ModuleManager " + Stage; @@ -1662,9 +1679,12 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) if (nextYield < Time.realtimeSinceStartup) { nextYield = Time.realtimeSinceStartup + yieldInterval; + StatusUpdate(); yield return null; } } + StatusUpdate(); + yield return null; } // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 From 8c062c18f6a2087d26287deb03194c257e6fdf6c Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 14:04:42 +0200 Subject: [PATCH 012/342] Minor cleanup --- moduleManager.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 4a24fe99..15e7896e 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -562,7 +562,6 @@ public override bool IsReady() { patchSw.Stop(); log("Ran in " + ((float)patchSw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); - } return ready; } @@ -2339,10 +2338,10 @@ private ConfigNode RecurseNodeSearch(string path, ConfigNode currentNode) if (constraint.Length > 0) { // get the first one matching - ConfigNode n, last = null; + ConfigNode last = null; while (true) { - n = FindConfigNodeIn(currentNode, nodeType, nodeName, index++); + ConfigNode n = FindConfigNodeIn(currentNode, nodeType, nodeName, index++); if (n == last || n == null) { currentNode = null; @@ -2493,10 +2492,10 @@ private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode cu if (constraint.Length > 0) { // get the first one matching - ConfigNode n, last = null; + ConfigNode last = null; while (true) { - n = FindConfigNodeIn(currentNode, nodeType, nodeName, index++); + ConfigNode n = FindConfigNodeIn(currentNode, nodeType, nodeName, index++); if (n == last || n == null) break; if (CheckConstraints(n, constraint)) From de3a659fca8ebc5885b18e1ac41dce3ab42fb1ad Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 8 Oct 2016 14:05:52 +0200 Subject: [PATCH 013/342] v2.7.1 --- Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index d59acafe..b67b2a8d 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyVersion("2.7.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 0f8ea05843dc14dc5481d2508496518aa080d8c1 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sun, 9 Oct 2016 15:23:45 +0200 Subject: [PATCH 014/342] V2.7.2 - put back the mods list/SHA log even when the cache is used --- Properties/AssemblyInfo.cs | 2 +- moduleManager.cs | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index b67b2a8d..207caf83 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.7.1")] +[assembly: AssemblyVersion("2.7.2")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, diff --git a/moduleManager.cs b/moduleManager.cs index 15e7896e..9666414b 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -769,16 +769,17 @@ private IEnumerator ProcessPatch(bool blocking) #if DEBUG //useCache = false; #endif - if (!useCache) - { - status = "Pre patch init"; - log(status); - yield return null; - List excludePaths = PrePatchInit(); + status = "Pre patch init"; + log(status); + yield return null; + + List excludePaths = PrePatchInit(); - yield return null; + if (!useCache) + { + yield return null; // If we don't use the cache then it is best to clean the PartDatabase.cfg if (!keepPartDB && File.Exists(partDatabasePath)) From 854aacbfbb8a2e5cffa6dfb99ff3bbdad68f447b Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 5 Nov 2016 11:11:07 +0100 Subject: [PATCH 015/342] v2.7.3 Fix the regex for @value,* = --- Properties/AssemblyInfo.cs | 2 +- moduleManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 207caf83..9182a96d 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.7.2")] +[assembly: AssemblyVersion("2.7.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, diff --git a/moduleManager.cs b/moduleManager.cs index 9666414b..de2a4b95 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -1688,7 +1688,7 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) } // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*]+(?:,\D[\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s([+\-*/^!]))?"); + private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*]+(?:,[^*\d][\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s([+\-*/^!]))?"); // Path is group 1, operator is group 5 private static Regex parseAssign = new Regex(@"(.*)(?:\s)+([+\-*/^!])?"); From 6fcbb626780942592c189ba698d97270bed109a2 Mon Sep 17 00:00:00 2001 From: Arne Peirs Date: Wed, 9 Nov 2016 18:54:57 +0100 Subject: [PATCH 016/342] Fix typos (#63) --- CustomConfigsManager.cs | 4 ++-- moduleManager.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CustomConfigsManager.cs b/CustomConfigsManager.cs index 2532c43e..2ceba915 100644 --- a/CustomConfigsManager.cs +++ b/CustomConfigsManager.cs @@ -11,13 +11,13 @@ internal void Start() { if (HighLogic.CurrentGame.Parameters.Career.TechTreeUrl != MMPatchLoader.techTreeFile && File.Exists(MMPatchLoader.techTreePath)) { - log("Setting moddeed tech tree as the active one"); + log("Setting modded tech tree as the active one"); HighLogic.CurrentGame.Parameters.Career.TechTreeUrl = MMPatchLoader.techTreeFile; } if (PhysicsGlobals.PhysicsDatabaseFilename != MMPatchLoader.physicsFile && File.Exists(MMPatchLoader.physicsPath)) { - log("Setting moddeed physics as the active one"); + log("Setting modded physics as the active one"); PhysicsGlobals.PhysicsDatabaseFilename = MMPatchLoader.physicsFile; diff --git a/moduleManager.cs b/moduleManager.cs index de2a4b95..b1ed0544 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -709,7 +709,7 @@ private List PrePatchInit() catch (ArgumentOutOfRangeException) { log("Skipping :FOR init for line " + name + - ". The line most likely contain a space that should be removed"); + ". The line most likely contains a space that should be removed"); errorCount++; } } @@ -1988,7 +1988,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) } else { - log("Error - Cannot parse variable search when replacing (%) key " + valName + " = " + + log("Error - Cannot parse variable search when replacing (&) key " + valName + " = " + modVal.value); errorCount++; } From 833e09bfa61e03e21ab4f1d83a163cd2c19078f2 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Mon, 14 Nov 2016 19:16:16 +0100 Subject: [PATCH 017/342] Fix #64 - Targeting all values applied the operation more than it should --- moduleManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index de2a4b95..f7f722e7 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -1821,6 +1821,11 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) } } + int valCount = 0; + for (int i=0; i Date: Mon, 14 Nov 2016 19:16:37 +0100 Subject: [PATCH 018/342] v2.7.4 --- Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 9182a96d..7ba97a9c 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.7.3")] +[assembly: AssemblyVersion("2.7.4")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 8f7fa2698725fb01b8a10ad9c465f2568c64e989 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Tue, 29 Nov 2016 20:14:40 +0100 Subject: [PATCH 019/342] v2.7.5 Reload trait config after the patching --- Properties/AssemblyInfo.cs | 2 +- moduleManager.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 7ba97a9c..775e2bbc 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.7.4")] +[assembly: AssemblyVersion("2.7.5")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, diff --git a/moduleManager.cs b/moduleManager.cs index 1f72928c..17e3a1f2 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -883,6 +883,9 @@ private IEnumerator ProcessPatch(bool blocking) log("Reloading resources definitions"); PartResourceLibrary.Instance.LoadDefinitions(); + log("Reloading Trait configs"); + GameDatabase.Instance.ExperienceConfigs.LoadTraitConfigs(); + foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks) { try From ca5288be87ca02e59b62c62fc5171dce2a768efe Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Mon, 3 Apr 2017 12:11:26 -0700 Subject: [PATCH 020/342] KSP 1.3 changes (#66) * Add names to dialog windows Now required * Adjust MMPatchLoaderIndex A new LoadingSystem was added at the beginning (FontLoader). This change ensures that MM will always be after the GameDatabase regardless. * Fix position of MM info in loading screen Things seem to have moved * Remove unused field * Press Alt+F11 again to dismiss the menu Apparently this wasn't a feature before (at least not recently) but pretty simple to implement --- moduleManager.cs | 81 +++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 17e3a1f2..a0f241cf 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -35,11 +35,10 @@ public class ModuleManager : MonoBehaviour private Sprite[] catFrames; private Texture2D rainbow; - - private int activePos = 0; - private bool nyan = false; + private PopupDialog menu; + #endregion state #region Top Level - Update @@ -108,7 +107,9 @@ internal void Awake() MMPatchLoader loader = aGameObject.AddComponent(); log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); - list.Insert(1, loader); + + int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); + list.Insert(gameDatabaseIndex + 1, loader); } nyan = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1) @@ -237,36 +238,46 @@ internal void Update() && (HighLogic.LoadedScene == GameScenes.SPACECENTER || HighLogic.LoadedScene == GameScenes.MAINMENU) && !inRnDCenter) { - PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), - new Vector2(0.5f, 0.5f), - new MultiOptionDialog("", - "ModuleManager", - HighLogic.UISkin, - new Rect(0.5f, 0.5f, 150f, 60f), - new DialogGUIFlexibleSpace(), - new DialogGUIVerticalLayout( + if (menu == null) + { + menu = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + new MultiOptionDialog( + "ModuleManagerMenu", + "", + "ModuleManager", + HighLogic.UISkin, + new Rect(0.5f, 0.5f, 150f, 60f), new DialogGUIFlexibleSpace(), - new DialogGUIButton("Reload Database", - delegate - { - MMPatchLoader.keepPartDB = false; - StartCoroutine(DataBaseReloadWithMM()); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Quick Reload Database", - delegate - { - MMPatchLoader.keepPartDB = true; - StartCoroutine(DataBaseReloadWithMM()); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Dump Database to Files", - delegate - { - StartCoroutine(DataBaseReloadWithMM(true)); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Close",() => {}, 140.0f, 30.0f, true) - )), - false, - HighLogic.UISkin); + new DialogGUIVerticalLayout( + new DialogGUIFlexibleSpace(), + new DialogGUIButton("Reload Database", + delegate + { + MMPatchLoader.keepPartDB = false; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Quick Reload Database", + delegate + { + MMPatchLoader.keepPartDB = true; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Dump Database to Files", + delegate + { + StartCoroutine(DataBaseReloadWithMM(true)); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Close", () => { }, 140.0f, 30.0f, true) + )), + false, + HighLogic.UISkin); + } + else + { + menu.Dismiss(); + menu = null; + } } if (totalTime.IsRunning && HighLogic.LoadedScene == GameScenes.MAINMENU) @@ -277,7 +288,7 @@ internal void Update() Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND; } - float offsetY = Mathf.FloorToInt(0.3f * Screen.height); + float offsetY = Mathf.FloorToInt(0.23f * Screen.height); float h; if (warning) { @@ -423,7 +434,7 @@ public bool ElectionAndCheck() string status = "You have old versions of Module Manager (older than 1.5) or MMSarbianExt.\nYou will need to remove them for Module Manager and the mods using it to work\nExit KSP and delete those files :\n" + String.Join("\n", badPaths.ToArray()); - PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); + PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "ModuleManagerOldVersions", "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); log("Old version of Module Manager present. Stopping"); return false; } From a3ccdffc895a377c3ffd3c37da974bbc7cec2cad Mon Sep 17 00:00:00 2001 From: shadowmage45 Date: Sat, 6 May 2017 00:10:26 -0600 Subject: [PATCH 021/342] Reload PartUpgrade System after patching (#70) As the part-upgrade data is initially populated prior to ModuleManager patching, this fix allows for the patches that are applied to the PARTUPGRADE nodes to be reloaded for use by the PartUpgrade system. With this fix in place the tech-nodes, names, descriptions, etc, for the part-upgrade parts located on the tech tree will use the proper post-patching config data. This solution has been tested to work properly when used directly from a ModuleManagerPostLoad callback. Fix for problems discovered in KSP-RO/RealismOverhaul/#1628 --- moduleManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/moduleManager.cs b/moduleManager.cs index a0f241cf..75ce4980 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -367,6 +367,8 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) MMPatchLoader.Instance.needsUnsatisfiedCount + " exceptionCount=" + MMPatchLoader.Instance.exceptionCount); PartResourceLibrary.Instance.LoadDefinitions(); + + PartUpgradeManager.Handler.FillUpgrades(); if (dump) OutputAllConfigs(); @@ -896,6 +898,9 @@ private IEnumerator ProcessPatch(bool blocking) log("Reloading Trait configs"); GameDatabase.Instance.ExperienceConfigs.LoadTraitConfigs(); + + log("Reloading Part Upgrades"); + PartUpgradeManager.Handler.FillUpgrades(); foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks) { From b301cb997d098eede0f8cdf68f1e18583d67b2ef Mon Sep 17 00:00:00 2001 From: sarbian Date: Sat, 6 May 2017 08:12:37 +0200 Subject: [PATCH 022/342] v2.7.6 --- Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 775e2bbc..a5916759 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.7.5")] +[assembly: AssemblyVersion("2.7.6")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 29df624348391373485a82fec75e273ceed30648 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 6 May 2017 08:16:53 +0200 Subject: [PATCH 023/342] Temp revert of 1.3 changes to release a 1.2 patch --- moduleManager.cs | 81 +++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 46 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 75ce4980..2e269652 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -35,9 +35,10 @@ public class ModuleManager : MonoBehaviour private Sprite[] catFrames; private Texture2D rainbow; - private bool nyan = false; - private PopupDialog menu; + private int activePos = 0; + + private bool nyan = false; #endregion state @@ -107,9 +108,7 @@ internal void Awake() MMPatchLoader loader = aGameObject.AddComponent(); log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); - - int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); - list.Insert(gameDatabaseIndex + 1, loader); + list.Insert(1, loader); } nyan = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1) @@ -238,46 +237,36 @@ internal void Update() && (HighLogic.LoadedScene == GameScenes.SPACECENTER || HighLogic.LoadedScene == GameScenes.MAINMENU) && !inRnDCenter) { - if (menu == null) - { - menu = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), - new Vector2(0.5f, 0.5f), - new MultiOptionDialog( - "ModuleManagerMenu", - "", - "ModuleManager", - HighLogic.UISkin, - new Rect(0.5f, 0.5f, 150f, 60f), + PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + new MultiOptionDialog("", + "ModuleManager", + HighLogic.UISkin, + new Rect(0.5f, 0.5f, 150f, 60f), + new DialogGUIFlexibleSpace(), + new DialogGUIVerticalLayout( new DialogGUIFlexibleSpace(), - new DialogGUIVerticalLayout( - new DialogGUIFlexibleSpace(), - new DialogGUIButton("Reload Database", - delegate - { - MMPatchLoader.keepPartDB = false; - StartCoroutine(DataBaseReloadWithMM()); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Quick Reload Database", - delegate - { - MMPatchLoader.keepPartDB = true; - StartCoroutine(DataBaseReloadWithMM()); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Dump Database to Files", - delegate - { - StartCoroutine(DataBaseReloadWithMM(true)); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Close", () => { }, 140.0f, 30.0f, true) - )), - false, - HighLogic.UISkin); - } - else - { - menu.Dismiss(); - menu = null; - } + new DialogGUIButton("Reload Database", + delegate + { + MMPatchLoader.keepPartDB = false; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Quick Reload Database", + delegate + { + MMPatchLoader.keepPartDB = true; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Dump Database to Files", + delegate + { + StartCoroutine(DataBaseReloadWithMM(true)); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Close",() => {}, 140.0f, 30.0f, true) + )), + false, + HighLogic.UISkin); } if (totalTime.IsRunning && HighLogic.LoadedScene == GameScenes.MAINMENU) @@ -288,7 +277,7 @@ internal void Update() Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND; } - float offsetY = Mathf.FloorToInt(0.23f * Screen.height); + float offsetY = Mathf.FloorToInt(0.3f * Screen.height); float h; if (warning) { @@ -436,7 +425,7 @@ public bool ElectionAndCheck() string status = "You have old versions of Module Manager (older than 1.5) or MMSarbianExt.\nYou will need to remove them for Module Manager and the mods using it to work\nExit KSP and delete those files :\n" + String.Join("\n", badPaths.ToArray()); - PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "ModuleManagerOldVersions", "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); + PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); log("Old version of Module Manager present. Stopping"); return false; } From d61f7255b76d601700b927d0e0ab74540d533e6c Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 6 May 2017 08:16:53 +0200 Subject: [PATCH 024/342] Revert "Temp revert of 1.3 changes to release a 1.2 patch" This reverts commit 29df624348391373485a82fec75e273ceed30648. --- moduleManager.cs | 81 +++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 2e269652..75ce4980 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -35,11 +35,10 @@ public class ModuleManager : MonoBehaviour private Sprite[] catFrames; private Texture2D rainbow; - - private int activePos = 0; - private bool nyan = false; + private PopupDialog menu; + #endregion state #region Top Level - Update @@ -108,7 +107,9 @@ internal void Awake() MMPatchLoader loader = aGameObject.AddComponent(); log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); - list.Insert(1, loader); + + int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); + list.Insert(gameDatabaseIndex + 1, loader); } nyan = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1) @@ -237,36 +238,46 @@ internal void Update() && (HighLogic.LoadedScene == GameScenes.SPACECENTER || HighLogic.LoadedScene == GameScenes.MAINMENU) && !inRnDCenter) { - PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), - new Vector2(0.5f, 0.5f), - new MultiOptionDialog("", - "ModuleManager", - HighLogic.UISkin, - new Rect(0.5f, 0.5f, 150f, 60f), - new DialogGUIFlexibleSpace(), - new DialogGUIVerticalLayout( + if (menu == null) + { + menu = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + new MultiOptionDialog( + "ModuleManagerMenu", + "", + "ModuleManager", + HighLogic.UISkin, + new Rect(0.5f, 0.5f, 150f, 60f), new DialogGUIFlexibleSpace(), - new DialogGUIButton("Reload Database", - delegate - { - MMPatchLoader.keepPartDB = false; - StartCoroutine(DataBaseReloadWithMM()); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Quick Reload Database", - delegate - { - MMPatchLoader.keepPartDB = true; - StartCoroutine(DataBaseReloadWithMM()); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Dump Database to Files", - delegate - { - StartCoroutine(DataBaseReloadWithMM(true)); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Close",() => {}, 140.0f, 30.0f, true) - )), - false, - HighLogic.UISkin); + new DialogGUIVerticalLayout( + new DialogGUIFlexibleSpace(), + new DialogGUIButton("Reload Database", + delegate + { + MMPatchLoader.keepPartDB = false; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Quick Reload Database", + delegate + { + MMPatchLoader.keepPartDB = true; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Dump Database to Files", + delegate + { + StartCoroutine(DataBaseReloadWithMM(true)); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Close", () => { }, 140.0f, 30.0f, true) + )), + false, + HighLogic.UISkin); + } + else + { + menu.Dismiss(); + menu = null; + } } if (totalTime.IsRunning && HighLogic.LoadedScene == GameScenes.MAINMENU) @@ -277,7 +288,7 @@ internal void Update() Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND; } - float offsetY = Mathf.FloorToInt(0.3f * Screen.height); + float offsetY = Mathf.FloorToInt(0.23f * Screen.height); float h; if (warning) { @@ -425,7 +436,7 @@ public bool ElectionAndCheck() string status = "You have old versions of Module Manager (older than 1.5) or MMSarbianExt.\nYou will need to remove them for Module Manager and the mods using it to work\nExit KSP and delete those files :\n" + String.Join("\n", badPaths.ToArray()); - PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); + PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "ModuleManagerOldVersions", "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); log("Old version of Module Manager present. Stopping"); return false; } From 851df260f9a80a41d3256f73167240af4b0c5e03 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Fri, 26 May 2017 11:03:09 +0200 Subject: [PATCH 025/342] v2.8.0 for KSP 1.3 --- Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index a5916759..7ec7c36d 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.7.6")] +[assembly: AssemblyVersion("2.8.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 9d5797bb7f97a69841e112856d2d9bd9e462096a Mon Sep 17 00:00:00 2001 From: Sarbian Date: Thu, 29 Jun 2017 22:06:52 +0200 Subject: [PATCH 026/342] Improve some cat related code and add -ncats cmd line option --- CatAnimator.cs | 38 +++++++++++++ CatMover.cs | 38 +++---------- CatOrbiter.cs | 135 +++++++++++++++++++++++++++++++++++++++++++++++ moduleManager.cs | 63 +++++++++++++++++----- 4 files changed, 232 insertions(+), 42 deletions(-) create mode 100644 CatAnimator.cs create mode 100644 CatOrbiter.cs diff --git a/CatAnimator.cs b/CatAnimator.cs new file mode 100644 index 00000000..5ff99c72 --- /dev/null +++ b/CatAnimator.cs @@ -0,0 +1,38 @@ +using System.Collections; +using UnityEngine; + +namespace ModuleManager +{ + class CatAnimator : MonoBehaviour + { + + public Sprite[] frames; + public float secFrame = 0.07f; + + private SpriteRenderer spriteRenderer; + private int spriteIdx; + + void Start() + { + spriteRenderer = this.GetComponent(); + spriteRenderer.sortingOrder = 3; + StartCoroutine(Animate()); + } + + + IEnumerator Animate() + { + if (frames.Length == 0) + yield return null; + + WaitForSeconds yield = new WaitForSeconds(secFrame); + + while (true) + { + spriteIdx = (spriteIdx + 1) % frames.Length; + spriteRenderer.sprite = frames[spriteIdx]; + yield return yield; + } + } + } +} diff --git a/CatMover.cs b/CatMover.cs index 12cba257..16d82170 100644 --- a/CatMover.cs +++ b/CatMover.cs @@ -11,38 +11,29 @@ public class CatMover : MonoBehaviour private float offsetY; public TrailRenderer trail; - - public Sprite[] frames; - public float secFrame = 0.07f; - private SpriteRenderer spriteRenderer; - private int spriteIdx; private int totalLenth = 100; private float activePos = 0; public float scale = 2; - private float time = 5; private float trailTime = 0.5f; - // Use this for initialization void Start() { trail = this.GetComponent(); - spriteRenderer = this.GetComponent(); - - spriteRenderer.sortingOrder = 3; trail.sortingOrder = 2; - offsetY = Mathf.FloorToInt(0.2f * Screen.height); - spos.z = -1; + spriteRenderer = this.GetComponent(); - StartCoroutine(Animate()); + offsetY = Mathf.FloorToInt(0.2f * Screen.height); + spos.z = -1; + trailTime = time / 4; totalLenth = (int) (Screen.width / time * trail.time) + 150; @@ -60,13 +51,13 @@ void Update() if (activePos > (Screen.width + totalLenth)) { - activePos = -frames[spriteIdx].textureRect.width; - trail.time = 0;; + activePos = -spriteRenderer.sprite.rect.width; + trail.time = 0; } float f = 2f * Mathf.PI * (activePos) / (Screen.width * 0.5f); - float heightOffset = Mathf.Sin(f) * (frames[spriteIdx].textureRect.height * scale); + float heightOffset = Mathf.Sin(f) * (spriteRenderer.sprite.rect.height * scale); spos.x = activePos; spos.y = offsetY + heightOffset; @@ -75,19 +66,6 @@ void Update() transform.rotation = Quaternion.Euler(0, 0, Mathf.Cos(f) * 0.25f * Mathf.PI * Mathf.Rad2Deg); } - IEnumerator Animate() - { - if (frames.Length == 0) - yield return null; - - WaitForSeconds yield = new WaitForSeconds(secFrame); - - while (true) - { - spriteIdx = (spriteIdx+1) % frames.Length; - spriteRenderer.sprite = frames[spriteIdx]; - yield return yield; - } - } + } } diff --git a/CatOrbiter.cs b/CatOrbiter.cs new file mode 100644 index 00000000..ad78157f --- /dev/null +++ b/CatOrbiter.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using KSP.UI; +using UnityEngine; +using Random = UnityEngine.Random; + +namespace ModuleManager +{ + class CatOrbiter : MonoBehaviour + { + private static List orbiters = new List(); + + private static CatOrbiter sun; + + private double _mass; + public Rigidbody2D rb; + + private Vector2d pos; + private Vector2d vel; + private Vector2d force; + private float scale = 1; + + private double G = 6.67408E-11; + + public double Mass + { + get { return _mass; } + set + { + _mass = value; + if (rb!=null) + rb.mass = (float)_mass; + } + } + + public void Init(CatOrbiter parent, float soi) + { + + TimingManager.FixedUpdateAdd(TimingManager.TimingStage.Earlyish, DoForces); + + orbiters.Add(this); + rb = gameObject.AddComponent(); + rb.isKinematic = true; + + if (orbiters.Count == 1) + { + sun = this; + Vector3 spos = new Vector3(Screen.width * 0.5f, Screen.height * 0.5f, -1); + transform.position = KSP.UI.UIMainCamera.Camera.ScreenToWorldPoint(spos); + Mass = 2E17; + pos.x = transform.position.x; + pos.y = transform.position.y; + } + else + { + Vector2 relativePos = Random.insideUnitCircle; + if (relativePos.magnitude < 0.2) + relativePos = relativePos.normalized * 0.3f; + Vector3 spos = UIMainCamera.Camera.WorldToScreenPoint(parent.transform.position) + (Vector3)(relativePos * soi); + spos.z = -1; + transform.position = UIMainCamera.Camera.ScreenToWorldPoint(spos); + + pos.x = transform.position.x; + pos.y = transform.position.y; + + //int scaleRange = 10; + // + //float factor = (1 + (scaleRange - 1) * Random.value); + // + //scale = parent.scale * factor / scaleRange; + scale = parent.scale * 0.6f; + + transform.localScale *= scale; + TrailRenderer trail = gameObject.GetComponent(); + trail.startWidth *= scale; + trail.endWidth *= scale; + + //Mass = factor * 2E16; + + Mass = parent.Mass * 0.025; + + Vector2d dist = parent.pos - pos; + double circularVel = Math.Sqrt(G * (Mass + parent.Mass) / dist.magnitude); + if (parent == sun) + circularVel *= Random.Range(0.9f, 1.1f); + Debug.Log("CatOrbiter " + circularVel.ToString("F3") + " " + Mass.ToString("F2") + " " + orbiters[0].Mass.ToString("F2") + " " + + dist.magnitude.ToString("F2")); + + Vector3d normal = (Random.value >= 0.3) ? Vector3d.back : Vector3d.forward; + + Vector3d vel3d = Vector3d.Cross(dist, normal).normalized * circularVel; + vel.x = parent.vel.x + vel3d.x; + vel.y = parent.vel.y + vel3d.y; + } + + rb.MovePosition(new Vector2((float)pos.x, (float)pos.y)); + } + + private void DoForces() + { + force = Vector2d.zero; + foreach (CatOrbiter cat in orbiters) + { + if (cat == this) + continue; + + // F = G * (m1 * m2) / r^2 + Vector2d dir = cat.pos - pos; + double f = G * (cat.Mass * Mass) / (dir.sqrMagnitude + 10); // +10 to avoid div/0 + force += (float)f * dir.normalized; + } + } + + + void OnDestroy() + { + orbiters.Remove(this); + TimingManager.FixedUpdateRemove(TimingManager.TimingStage.Earlyish, DoForces); + } + + void FixedUpdate() + { + //if (this == sun) + // return; + + vel += Time.fixedDeltaTime * force / Mass; + pos += Time.fixedDeltaTime * vel; + + rb.MovePosition(new Vector2((float)pos.x, (float)pos.y)); + + double angle = Math.Atan2(vel.y, vel.x) * Mathf.Rad2Deg; + rb.MoveRotation((float)angle); + } + } +} diff --git a/moduleManager.cs b/moduleManager.cs index 75ce4980..eec21e40 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -36,6 +36,7 @@ public class ModuleManager : MonoBehaviour private Texture2D rainbow; private bool nyan = false; + private bool nCats = false; private PopupDialog menu; @@ -116,6 +117,8 @@ internal void Awake() || (DateTime.Now < new DateTime(2016, 11, 1)) || Environment.GetCommandLineArgs().Contains("-nyan-nyan"); + nCats = Environment.GetCommandLineArgs().Contains("-ncats") ; + loadedInScene = true; } @@ -197,32 +200,68 @@ private void SendCatToLaunchBay() catFrames[i].name = "cat" + i; } + int scale = 1; + if (Screen.height >= 1080) + scale *= 2; + if (Screen.height > 1440) + scale *= 3; + + Physics2D.gravity = Vector2.zero; + + + if (!nCats) + { + GameObject cat = LaunchCat(scale); + CatMover catMover = cat.AddComponent(); + } + else + { + GameObject catSun = LaunchCat(scale); + CatOrbiter catSunOrbiter = catSun.AddComponent(); + catSunOrbiter.Init(null,0); + + int cats = UnityEngine.Random.Range(6, 10); + for (int i = 0; i < cats; i++) + { + GameObject cat = LaunchCat(scale); + CatOrbiter catOrbiter = cat.AddComponent(); + catOrbiter.Init(catSunOrbiter, Screen.height * 0.5f); + + int moons = UnityEngine.Random.Range(0, 4); + + for (int j = 0; j < moons; j++) + { + GameObject catMoon = LaunchCat(scale); + CatOrbiter catMoonOrbiter = catMoon.AddComponent(); + catMoonOrbiter.Init(catOrbiter, Screen.height * 0.06f); + } + } + } + } + + private GameObject LaunchCat(int scale) + { GameObject cat = new GameObject("NyanCat"); SpriteRenderer sr = cat.AddComponent(); TrailRenderer trail = cat.AddComponent(); - CatMover catMover = cat.AddComponent(); + CatAnimator catAnimator = cat.AddComponent(); sr.sprite = catFrames[0]; trail.material = new Material(Shader.Find("Particles/Alpha Blended")); + Debug.Log("material = " + trail.material); trail.material.mainTexture = rainbow; trail.time = 1.5f; - trail.startWidth = rainbow.height; - trail.endWidth = rainbow.height * 0.9f; + trail.startWidth = 0.6f * scale * rainbow.height; + trail.endWidth = 0.6f * scale * rainbow.height * 0.9f; cat.layer = LayerMask.NameToLayer("UI"); - catMover.frames = catFrames; - int scale = 70; - - scale *= 1; - if (Screen.height >= 1080) - scale *= 2; - if (Screen.height > 1440) - scale *= 3; + catAnimator.frames = catFrames; - cat.transform.localScale = scale * Vector3.one; + cat.transform.localScale = 70 * scale * Vector3.one; + return cat; } // Unsubscribe from events when the behavior dies From e5dce48298639dc7149b5a3a044e745731757c28 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Thu, 29 Jun 2017 22:07:34 +0200 Subject: [PATCH 027/342] Improve logging related to some exceptions --- moduleManager.cs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index eec21e40..89c8b859 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -444,7 +444,19 @@ private static void OutputAllConfigs() Directory.CreateDirectory(path); foreach (UrlDir.UrlConfig d in GameDatabase.Instance.root.AllConfigs) - File.WriteAllText(path + d.url.Replace('/', '.') + ".cfg", d.config.ToString()); + { + string file = d.url.Replace('/', '.').Replace(':', '.'); + string filePath = path + file + ".cfg"; + try + { + + File.WriteAllText(filePath, d.config.ToString()); + } + catch (Exception e) + { + log("Exception while trying to write the file " + filePath + "\n" + e); + } + } } #endregion GUI stuff. @@ -1130,8 +1142,14 @@ private void IsCacheUpToDate() filesha.ComputeHash(contentBytes); - filesSha.Add(files[i].url, BitConverter.ToString(filesha.Hash)); - + if (!filesSha.ContainsKey(files[i].url)) + { + filesSha.Add(files[i].url, BitConverter.ToString(filesha.Hash)); + } + else + { + log("Duplicate fileSha key. This should not append. The key is " + files[i].url); + } } // Hash the mods dll path so the checksum change if dlls are moved or removed (impact NEEDS) From e578500c0119e9ecf35037b09578c6e9b54efb8a Mon Sep 17 00:00:00 2001 From: Sarbian Date: Thu, 29 Jun 2017 22:08:11 +0200 Subject: [PATCH 028/342] Update project file --- ModuleManager.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ModuleManager.csproj b/ModuleManager.csproj index 1acb75ac..ed0b6bee 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -31,7 +31,9 @@ False + + From 8a097d20b5979d539cc4533bf4a2853456d1de04 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Thu, 29 Jun 2017 22:08:25 +0200 Subject: [PATCH 029/342] v2.8.1 --- Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 7ec7c36d..4ed5322e 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.8.0")] +[assembly: AssemblyVersion("2.8.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ceabfeb24a734382ef08eb4af5299b6bc99dbb14 Mon Sep 17 00:00:00 2001 From: nightingale Date: Thu, 29 Jun 2017 15:39:30 -0600 Subject: [PATCH 030/342] nCats needs more love. (#74) --- moduleManager.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/moduleManager.cs b/moduleManager.cs index 89c8b859..180e0373 100644 --- a/moduleManager.cs +++ b/moduleManager.cs @@ -112,12 +112,16 @@ internal void Awake() int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); list.Insert(gameDatabaseIndex + 1, loader); } - - nyan = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1) + + bool foolsDay = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1); + bool catDay = (DateTime.Now.Month == 2 && DateTime.Now.Day == 22); + nyan = foolsDay + || catDay || (DateTime.Now < new DateTime(2016, 11, 1)) || Environment.GetCommandLineArgs().Contains("-nyan-nyan"); - nCats = Environment.GetCommandLineArgs().Contains("-ncats") ; + nCats = catDay + || Environment.GetCommandLineArgs().Contains("-ncats") ; loadedInScene = true; } From e29c9cb61eb97df6e68bfea1bb6290d3ca49e97e Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Thu, 10 Aug 2017 06:19:51 -0700 Subject: [PATCH 031/342] Begin splitting files up (#76) * rename file most of it is MMPatchLoader so that's what it'll be * Remove corrupt #region It starts in one class and ends in another, I can't tell where it's really supposed to go * Move addon to its own file * Put cats in a box * Can has namespace * Promote business cat to manager * Unnecessary now * Old stuff --- CatAnimator.cs => Cats/CatAnimator.cs | 2 +- Cats/CatManager.cs | 113 ++++++ CatMover.cs => Cats/CatMover.cs | 2 +- CatOrbiter.cs => Cats/CatOrbiter.cs | 2 +- moduleManager.cs => MMPatchLoader.cs | 524 -------------------------- ModuleManager.cs | 429 +++++++++++++++++++++ ModuleManager.csproj | 10 +- 7 files changed, 551 insertions(+), 531 deletions(-) rename CatAnimator.cs => Cats/CatAnimator.cs (96%) create mode 100644 Cats/CatManager.cs rename CatMover.cs => Cats/CatMover.cs (98%) rename CatOrbiter.cs => Cats/CatOrbiter.cs (99%) rename moduleManager.cs => MMPatchLoader.cs (83%) create mode 100644 ModuleManager.cs diff --git a/CatAnimator.cs b/Cats/CatAnimator.cs similarity index 96% rename from CatAnimator.cs rename to Cats/CatAnimator.cs index 5ff99c72..ed5b2c70 100644 --- a/CatAnimator.cs +++ b/Cats/CatAnimator.cs @@ -1,7 +1,7 @@ using System.Collections; using UnityEngine; -namespace ModuleManager +namespace ModuleManager.Cats { class CatAnimator : MonoBehaviour { diff --git a/Cats/CatManager.cs b/Cats/CatManager.cs new file mode 100644 index 00000000..4ef37e45 --- /dev/null +++ b/Cats/CatManager.cs @@ -0,0 +1,113 @@ +using System; +using UnityEngine; + +namespace ModuleManager.Cats +{ + public static class CatManager + { + private static Sprite[] catFrames; + private static Texture2D rainbow; + private static int scale = 1; + + public static void LaunchCat() + { + InitCats(); + + GameObject cat = LaunchCat(scale); + CatMover catMover = cat.AddComponent(); + } + + public static void LaunchCats() + { + InitCats(); + + GameObject catSun = LaunchCat(scale); + CatOrbiter catSunOrbiter = catSun.AddComponent(); + catSunOrbiter.Init(null, 0); + + int cats = UnityEngine.Random.Range(6, 10); + for (int i = 0; i < cats; i++) + { + GameObject cat = LaunchCat(scale); + CatOrbiter catOrbiter = cat.AddComponent(); + catOrbiter.Init(catSunOrbiter, Screen.height * 0.5f); + + int moons = UnityEngine.Random.Range(0, 4); + + for (int j = 0; j < moons; j++) + { + GameObject catMoon = LaunchCat(scale); + CatOrbiter catMoonOrbiter = catMoon.AddComponent(); + catMoonOrbiter.Init(catOrbiter, Screen.height * 0.06f); + } + } + } + + private static void InitCats() + { + Texture2D[] tex = new Texture2D[12]; + for (int i = 0; i < tex.Length; i++) + { + tex[i] = new Texture2D(70, 42, TextureFormat.ARGB32, false); + } + tex[0].LoadImage(Properties.Resources.cat1); + tex[1].LoadImage(Properties.Resources.cat2); + tex[2].LoadImage(Properties.Resources.cat3); + tex[3].LoadImage(Properties.Resources.cat4); + tex[4].LoadImage(Properties.Resources.cat5); + tex[5].LoadImage(Properties.Resources.cat6); + tex[6].LoadImage(Properties.Resources.cat7); + tex[7].LoadImage(Properties.Resources.cat8); + tex[8].LoadImage(Properties.Resources.cat9); + tex[9].LoadImage(Properties.Resources.cat10); + tex[10].LoadImage(Properties.Resources.cat11); + tex[11].LoadImage(Properties.Resources.cat12); + + rainbow = new Texture2D(39, 36, TextureFormat.ARGB32, false); + rainbow.LoadImage(Properties.Resources.rainbow); + rainbow.Apply(); + + catFrames = new Sprite[12]; + + for (int i = 0; i < tex.Length; i++) + { + tex[i].Apply(); + catFrames[i] = Sprite.Create(tex[i], new Rect(0, 0, tex[i].width, tex[i].height), new Vector2(.5f, .5f)); + catFrames[i].name = "cat" + i; + } + + scale = 1; + if (Screen.height >= 1080) + scale *= 2; + if (Screen.height > 1440) + scale *= 3; + + Physics2D.gravity = Vector2.zero; + } + + private static GameObject LaunchCat(int scale) + { + GameObject cat = new GameObject("NyanCat"); + SpriteRenderer sr = cat.AddComponent(); + TrailRenderer trail = cat.AddComponent(); + CatAnimator catAnimator = cat.AddComponent(); + + sr.sprite = catFrames[0]; + + trail.material = new Material(Shader.Find("Particles/Alpha Blended")); + + Debug.Log("material = " + trail.material); + trail.material.mainTexture = rainbow; + trail.time = 1.5f; + trail.startWidth = 0.6f * scale * rainbow.height; + trail.endWidth = 0.6f * scale * rainbow.height * 0.9f; + + cat.layer = LayerMask.NameToLayer("UI"); + + catAnimator.frames = catFrames; + + cat.transform.localScale = 70 * scale * Vector3.one; + return cat; + } + } +} diff --git a/CatMover.cs b/Cats/CatMover.cs similarity index 98% rename from CatMover.cs rename to Cats/CatMover.cs index 16d82170..c6516243 100644 --- a/CatMover.cs +++ b/Cats/CatMover.cs @@ -1,7 +1,7 @@ using System.Collections; using UnityEngine; -namespace ModuleManager +namespace ModuleManager.Cats { public class CatMover : MonoBehaviour { diff --git a/CatOrbiter.cs b/Cats/CatOrbiter.cs similarity index 99% rename from CatOrbiter.cs rename to Cats/CatOrbiter.cs index ad78157f..5c20e705 100644 --- a/CatOrbiter.cs +++ b/Cats/CatOrbiter.cs @@ -4,7 +4,7 @@ using UnityEngine; using Random = UnityEngine.Random; -namespace ModuleManager +namespace ModuleManager.Cats { class CatOrbiter : MonoBehaviour { diff --git a/moduleManager.cs b/MMPatchLoader.cs similarity index 83% rename from moduleManager.cs rename to MMPatchLoader.cs index 180e0373..948edf13 100644 --- a/moduleManager.cs +++ b/MMPatchLoader.cs @@ -8,533 +8,11 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using TMPro; using UnityEngine; using Debug = UnityEngine.Debug; namespace ModuleManager { - [KSPAddon(KSPAddon.Startup.Instantly, false)] - public class ModuleManager : MonoBehaviour - { - #region state - - private bool inRnDCenter; - - private bool reloading; - - public bool showUI = false; - - private Rect windowPos = new Rect(80f, 60f, 240f, 40f); - - private string version = ""; - - //private Texture2D tex; - //private Texture2D tex2; - - private Sprite[] catFrames; - private Texture2D rainbow; - - private bool nyan = false; - private bool nCats = false; - - private PopupDialog menu; - - #endregion state - - #region Top Level - Update - - private static bool loadedInScene; - - internal void OnRnDCenterSpawn() - { - inRnDCenter = true; - } - - internal void OnRnDCenterDeSpawn() - { - inRnDCenter = false; - } - - public static void log(String s) - { - print("[ModuleManager] " + s); - } - - private Stopwatch totalTime = new Stopwatch(); - - internal void Awake() - { - totalTime.Start(); - - // Allow loading the background in the laoding screen - Application.runInBackground = true; - - // Ensure that only one copy of the service is run per scene change. - if (loadedInScene || !ElectionAndCheck()) - { - Assembly currentAssembly = Assembly.GetExecutingAssembly(); - log("Multiple copies of current version. Using the first copy. Version: " + - currentAssembly.GetName().Version); - Destroy(gameObject); - return; - } - DontDestroyOnLoad(gameObject); - - Version v = Assembly.GetExecutingAssembly().GetName().Version; - version = v.Major + "." + v.Minor + "." + v.Build; - - // Subscribe to the RnD center spawn/deSpawn events - GameEvents.onGUIRnDComplexSpawn.Add(OnRnDCenterSpawn); - GameEvents.onGUIRnDComplexDespawn.Add(OnRnDCenterDeSpawn); - - - LoadingScreen screen = FindObjectOfType(); - if (screen == null) - { - log("Can't find LoadingScreen type. Aborting ModuleManager execution"); - return; - } - List list = LoadingScreen.Instance.loaders; - - if (list != null) - { - // So you can insert a LoadingSystem object in this list at any point. - // GameDatabase is first in the list, and PartLoader is second - // We could insert ModuleManager after GameDatabase to get it to run there - // and SaveGameFixer after PartLoader. - - GameObject aGameObject = new GameObject("ModuleManager"); - MMPatchLoader loader = aGameObject.AddComponent(); - - log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); - - int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); - list.Insert(gameDatabaseIndex + 1, loader); - } - - bool foolsDay = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1); - bool catDay = (DateTime.Now.Month == 2 && DateTime.Now.Day == 22); - nyan = foolsDay - || catDay - || (DateTime.Now < new DateTime(2016, 11, 1)) - || Environment.GetCommandLineArgs().Contains("-nyan-nyan"); - - nCats = catDay - || Environment.GetCommandLineArgs().Contains("-ncats") ; - - loadedInScene = true; - } - - private TextMeshProUGUI status; - private TextMeshProUGUI errors; - private TextMeshProUGUI warning; - - - private void Start() - { - SendCatToLaunchBay(); - - Canvas canvas = LoadingScreen.Instance.GetComponentInChildren(); - - status = CreateTextObject(canvas, "MMStatus"); - errors = CreateTextObject(canvas, "MMErrors"); - warning = CreateTextObject(canvas, "MMWarning"); - warning.text = ""; - - //if (Versioning.version_major == 1 && Versioning.version_minor == 0 && Versioning.Revision == 5 && Versioning.BuildID == 1024) - //{ - // warning.text = "Your KSP 1.0.5 is running on build 1024. You should upgrade to build 1028 to avoid problems with addons."; - // //if (GUI.Button(new Rect(Screen.width / 2f - 100, offsetY, 200, 20), "Click to open the Forum thread")) - // // Application.OpenURL("http://forum.kerbalspaceprogram.com/index.php?/topic/124998-silent-patch-for-ksp-105-published/"); - //} - } - - private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) - { - GameObject statusGameObject = new GameObject(name); - TextMeshProUGUI text = statusGameObject.AddComponent(); - text.text = "STATUS"; - text.fontSize = 16; - text.autoSizeTextContainer = true; - text.font = Resources.Load("Fonts/Calibri SDF", typeof(TMP_FontAsset)) as TMP_FontAsset; - text.alignment = TextAlignmentOptions.Center; - text.enableWordWrapping = false; - text.isOverlay = true; - statusGameObject.transform.SetParent(canvas.transform); - - return text; - } - - private void SendCatToLaunchBay() - { - if (!nyan) - return; - - // Nyancat are GO !!! - - Texture2D[] tex = new Texture2D[12]; - for (int i = 0; i < tex.Length; i++) - { - tex[i] = new Texture2D(70, 42, TextureFormat.ARGB32, false); - } - tex[0].LoadImage(Properties.Resources.cat1); - tex[1].LoadImage(Properties.Resources.cat2); - tex[2].LoadImage(Properties.Resources.cat3); - tex[3].LoadImage(Properties.Resources.cat4); - tex[4].LoadImage(Properties.Resources.cat5); - tex[5].LoadImage(Properties.Resources.cat6); - tex[6].LoadImage(Properties.Resources.cat7); - tex[7].LoadImage(Properties.Resources.cat8); - tex[8].LoadImage(Properties.Resources.cat9); - tex[9].LoadImage(Properties.Resources.cat10); - tex[10].LoadImage(Properties.Resources.cat11); - tex[11].LoadImage(Properties.Resources.cat12); - - rainbow = new Texture2D(39, 36, TextureFormat.ARGB32, false); - rainbow.LoadImage(Properties.Resources.rainbow); - rainbow.Apply(); - - catFrames = new Sprite[12]; - - for (int i = 0; i < tex.Length; i++) - { - tex[i].Apply(); - catFrames[i] = Sprite.Create(tex[i], new Rect(0, 0, tex[i].width, tex[i].height), new Vector2(.5f, .5f)); - catFrames[i].name = "cat" + i; - } - - int scale = 1; - if (Screen.height >= 1080) - scale *= 2; - if (Screen.height > 1440) - scale *= 3; - - Physics2D.gravity = Vector2.zero; - - - if (!nCats) - { - GameObject cat = LaunchCat(scale); - CatMover catMover = cat.AddComponent(); - } - else - { - GameObject catSun = LaunchCat(scale); - CatOrbiter catSunOrbiter = catSun.AddComponent(); - catSunOrbiter.Init(null,0); - - int cats = UnityEngine.Random.Range(6, 10); - for (int i = 0; i < cats; i++) - { - GameObject cat = LaunchCat(scale); - CatOrbiter catOrbiter = cat.AddComponent(); - catOrbiter.Init(catSunOrbiter, Screen.height * 0.5f); - - int moons = UnityEngine.Random.Range(0, 4); - - for (int j = 0; j < moons; j++) - { - GameObject catMoon = LaunchCat(scale); - CatOrbiter catMoonOrbiter = catMoon.AddComponent(); - catMoonOrbiter.Init(catOrbiter, Screen.height * 0.06f); - } - } - } - } - - private GameObject LaunchCat(int scale) - { - GameObject cat = new GameObject("NyanCat"); - SpriteRenderer sr = cat.AddComponent(); - TrailRenderer trail = cat.AddComponent(); - CatAnimator catAnimator = cat.AddComponent(); - - sr.sprite = catFrames[0]; - - trail.material = new Material(Shader.Find("Particles/Alpha Blended")); - - Debug.Log("material = " + trail.material); - trail.material.mainTexture = rainbow; - trail.time = 1.5f; - trail.startWidth = 0.6f * scale * rainbow.height; - trail.endWidth = 0.6f * scale * rainbow.height * 0.9f; - - cat.layer = LayerMask.NameToLayer("UI"); - - catAnimator.frames = catFrames; - - cat.transform.localScale = 70 * scale * Vector3.one; - return cat; - } - - // Unsubscribe from events when the behavior dies - internal void OnDestroy() - { - GameEvents.onGUIRnDComplexSpawn.Remove(OnRnDCenterSpawn); - GameEvents.onGUIRnDComplexDespawn.Remove(OnRnDCenterDeSpawn); - } - - internal void Update() - { - if (GameSettings.MODIFIER_KEY.GetKey() && Input.GetKeyDown(KeyCode.F11) - && (HighLogic.LoadedScene == GameScenes.SPACECENTER || HighLogic.LoadedScene == GameScenes.MAINMENU) - && !inRnDCenter) - { - if (menu == null) - { - menu = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), - new Vector2(0.5f, 0.5f), - new MultiOptionDialog( - "ModuleManagerMenu", - "", - "ModuleManager", - HighLogic.UISkin, - new Rect(0.5f, 0.5f, 150f, 60f), - new DialogGUIFlexibleSpace(), - new DialogGUIVerticalLayout( - new DialogGUIFlexibleSpace(), - new DialogGUIButton("Reload Database", - delegate - { - MMPatchLoader.keepPartDB = false; - StartCoroutine(DataBaseReloadWithMM()); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Quick Reload Database", - delegate - { - MMPatchLoader.keepPartDB = true; - StartCoroutine(DataBaseReloadWithMM()); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Dump Database to Files", - delegate - { - StartCoroutine(DataBaseReloadWithMM(true)); - }, 140.0f, 30.0f, true), - new DialogGUIButton("Close", () => { }, 140.0f, 30.0f, true) - )), - false, - HighLogic.UISkin); - } - else - { - menu.Dismiss(); - menu = null; - } - } - - if (totalTime.IsRunning && HighLogic.LoadedScene == GameScenes.MAINMENU) - { - totalTime.Stop(); - log("Total loading Time = " + ((float)totalTime.ElapsedMilliseconds / 1000).ToString("F3") + "s"); - - Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND; - } - - float offsetY = Mathf.FloorToInt(0.23f * Screen.height); - float h; - if (warning) - { - warning.transform.localPosition = new Vector3(0, -offsetY); - h = warning.textBounds.size.y; - if (h > 0) - offsetY = offsetY + h + 10; - } - - if (status) - { - status.transform.localPosition = new Vector3(0, -offsetY); - status.text = MMPatchLoader.Instance.status; - - h = status.textBounds.size.y; - if (h > 0) - offsetY = offsetY + h + 10; - } - - if (errors) - { - errors.transform.localPosition = new Vector3(0, -offsetY); - errors.text = MMPatchLoader.Instance.errors; - } - - if (reloading) - { - float percent = 0; - if (!GameDatabase.Instance.IsReady()) - percent = GameDatabase.Instance.ProgressFraction(); - else if (!MMPatchLoader.Instance.IsReady()) - percent = 1f + MMPatchLoader.Instance.ProgressFraction(); - else if (!PartLoader.Instance.IsReady()) - percent = 2f + PartLoader.Instance.ProgressFraction(); - - int intPercent = Mathf.CeilToInt(percent * 100f / 3f); - ScreenMessages.PostScreenMessage("Database reloading " + intPercent + "%", Time.deltaTime, - ScreenMessageStyle.UPPER_CENTER); - } - } - - #region GUI stuff. - - internal static IntPtr intPtr = new IntPtr(long.MaxValue); - /* Not required anymore. At least - public static bool IsABadIdea() - { - return (intPtr.ToInt64() == long.MaxValue) && (Environment.OSVersion.Platform == PlatformID.Win32NT); - } - */ - - private IEnumerator DataBaseReloadWithMM(bool dump = false) - { - reloading = true; - - QualitySettings.vSyncCount = 0; - Application.targetFrameRate = -1; - - ScreenMessages.PostScreenMessage("Database reloading started", 1, ScreenMessageStyle.UPPER_CENTER); - yield return null; - - GameDatabase.Instance.Recompile = true; - GameDatabase.Instance.StartLoad(); - - // wait for it to finish - while (!GameDatabase.Instance.IsReady()) - yield return null; - - MMPatchLoader.Instance.StartLoad(); - - while (!MMPatchLoader.Instance.IsReady()) - yield return null; - - log("DB Reload OK with patchCount=" + MMPatchLoader.Instance.patchedNodeCount + " errorCount=" + - MMPatchLoader.Instance.errorCount + " needsUnsatisfiedCount=" + - MMPatchLoader.Instance.needsUnsatisfiedCount + " exceptionCount=" + MMPatchLoader.Instance.exceptionCount); - - PartResourceLibrary.Instance.LoadDefinitions(); - - PartUpgradeManager.Handler.FillUpgrades(); - - if (dump) - OutputAllConfigs(); - - PartLoader.Instance.StartLoad(); - - while (!PartLoader.Instance.IsReady()) - yield return null; - - // Needs more work. - //ConfigNode game = HighLogic.CurrentGame.config.GetNode("GAME"); - - //if (game != null && ResearchAndDevelopment.Instance != null) - //{ - // ScreenMessages.PostScreenMessage("GAME found"); - // ConfigNode scenario = game.GetNodes("SCENARIO").FirstOrDefault((ConfigNode n) => n.name == "ResearchAndDevelopment"); - // if (scenario != null) - // { - // ScreenMessages.PostScreenMessage("SCENARIO found"); - // ResearchAndDevelopment.Instance.OnLoad(scenario); - // } - //} - - QualitySettings.vSyncCount = GameSettings.SYNC_VBL; - Application.targetFrameRate = GameSettings.FRAMERATE_LIMIT; - reloading = false; - ScreenMessages.PostScreenMessage("Database reloading finished", 1, ScreenMessageStyle.UPPER_CENTER); - } - - private static void OutputAllConfigs() - { - string path = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "_MMCfgOutput" - + Path.DirectorySeparatorChar; - Directory.CreateDirectory(path); - - foreach (UrlDir.UrlConfig d in GameDatabase.Instance.root.AllConfigs) - { - string file = d.url.Replace('/', '.').Replace(':', '.'); - string filePath = path + file + ".cfg"; - try - { - - File.WriteAllText(filePath, d.config.ToString()); - } - catch (Exception e) - { - log("Exception while trying to write the file " + filePath + "\n" + e); - } - } - } - - #endregion GUI stuff. - - public bool ElectionAndCheck() - { - #region Type election - - // TODO : Move the old version check in a process that call Update. - - // Check for old version and MMSarbianExt - IEnumerable oldMM = - AssemblyLoader.loadedAssemblies.Where( - a => a.assembly.GetName().Name == Assembly.GetExecutingAssembly().GetName().Name) - .Where(a => a.assembly.GetName().Version.CompareTo(new System.Version(1, 5, 0)) == -1); - IEnumerable oldAssemblies = - oldMM.Concat(AssemblyLoader.loadedAssemblies.Where(a => a.assembly.GetName().Name == "MMSarbianExt")); - if (oldAssemblies.Any()) - { - IEnumerable badPaths = - oldAssemblies.Select(a => a.path) - .Select( - p => - Uri.UnescapeDataString( - new Uri(Path.GetFullPath(KSPUtil.ApplicationRootPath)).MakeRelativeUri(new Uri(p)) - .ToString() - .Replace('/', Path.DirectorySeparatorChar))); - string status = - "You have old versions of Module Manager (older than 1.5) or MMSarbianExt.\nYou will need to remove them for Module Manager and the mods using it to work\nExit KSP and delete those files :\n" + - String.Join("\n", badPaths.ToArray()); - PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "ModuleManagerOldVersions", "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); - log("Old version of Module Manager present. Stopping"); - return false; - } - - - //PopupDialog.SpawnPopupDialog(new Vector2(0.1f, 1f), new Vector2(0.2f, 1f), "Test of the dialog", "Stuff", "OK", false, UISkinManager.defaultSkin); - - Assembly currentAssembly = Assembly.GetExecutingAssembly(); - IEnumerable eligible = from a in AssemblyLoader.loadedAssemblies - let ass = a.assembly - where ass.GetName().Name == currentAssembly.GetName().Name - orderby ass.GetName().Version descending, a.path ascending - select a; - - // Elect the newest loaded version of MM to process all patch files. - // If there is a newer version loaded then don't do anything - // If there is a same version but earlier in the list, don't do anything either. - if (eligible.First().assembly != currentAssembly) - { - //loaded = true; - log("version " + currentAssembly.GetName().Version + " at " + currentAssembly.Location + - " lost the election"); - Destroy(gameObject); - return false; - } - string candidates = ""; - foreach (AssemblyLoader.LoadedAssembly a in eligible) - { - if (currentAssembly.Location != a.path) - candidates += "Version " + a.assembly.GetName().Version + " " + a.path + " " + "\n"; - } - if (candidates.Length > 0) - { - log("version " + currentAssembly.GetName().Version + " at " + currentAssembly.Location + - " won the election against\n" + candidates); - } - - #endregion Type election - - return true; - } - } - public delegate void ModuleManagerPostPatchCallback(); [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] @@ -669,8 +147,6 @@ public void StartLoad(bool blocking) needsUnsatisfiedCount = 0; errorFiles = new Dictionary(); - #endregion Top Level - Update - ready = false; // DB check used to track the now fixed TextureReplacer corruption diff --git a/ModuleManager.cs b/ModuleManager.cs new file mode 100644 index 00000000..6b3d1b1a --- /dev/null +++ b/ModuleManager.cs @@ -0,0 +1,429 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using TMPro; +using UnityEngine; +using Debug = UnityEngine.Debug; +using ModuleManager.Cats; + +namespace ModuleManager +{ + [KSPAddon(KSPAddon.Startup.Instantly, false)] + public class ModuleManager : MonoBehaviour + { + #region state + + private bool inRnDCenter; + + private bool reloading; + + public bool showUI = false; + + private Rect windowPos = new Rect(80f, 60f, 240f, 40f); + + private string version = ""; + + //private Texture2D tex; + //private Texture2D tex2; + + private bool nyan = false; + private bool nCats = false; + + private PopupDialog menu; + + #endregion state + + private static bool loadedInScene; + + internal void OnRnDCenterSpawn() + { + inRnDCenter = true; + } + + internal void OnRnDCenterDeSpawn() + { + inRnDCenter = false; + } + + public static void log(String s) + { + print("[ModuleManager] " + s); + } + + private Stopwatch totalTime = new Stopwatch(); + + internal void Awake() + { + totalTime.Start(); + + // Allow loading the background in the laoding screen + Application.runInBackground = true; + + // Ensure that only one copy of the service is run per scene change. + if (loadedInScene || !ElectionAndCheck()) + { + Assembly currentAssembly = Assembly.GetExecutingAssembly(); + log("Multiple copies of current version. Using the first copy. Version: " + + currentAssembly.GetName().Version); + Destroy(gameObject); + return; + } + DontDestroyOnLoad(gameObject); + + Version v = Assembly.GetExecutingAssembly().GetName().Version; + version = v.Major + "." + v.Minor + "." + v.Build; + + // Subscribe to the RnD center spawn/deSpawn events + GameEvents.onGUIRnDComplexSpawn.Add(OnRnDCenterSpawn); + GameEvents.onGUIRnDComplexDespawn.Add(OnRnDCenterDeSpawn); + + + LoadingScreen screen = FindObjectOfType(); + if (screen == null) + { + log("Can't find LoadingScreen type. Aborting ModuleManager execution"); + return; + } + List list = LoadingScreen.Instance.loaders; + + if (list != null) + { + // So you can insert a LoadingSystem object in this list at any point. + // GameDatabase is first in the list, and PartLoader is second + // We could insert ModuleManager after GameDatabase to get it to run there + // and SaveGameFixer after PartLoader. + + GameObject aGameObject = new GameObject("ModuleManager"); + MMPatchLoader loader = aGameObject.AddComponent(); + + log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); + + int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); + list.Insert(gameDatabaseIndex + 1, loader); + } + + bool foolsDay = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1); + bool catDay = (DateTime.Now.Month == 2 && DateTime.Now.Day == 22); + nyan = foolsDay + || Environment.GetCommandLineArgs().Contains("-nyan-nyan"); + + nCats = catDay + || Environment.GetCommandLineArgs().Contains("-ncats"); + + loadedInScene = true; + } + + private TextMeshProUGUI status; + private TextMeshProUGUI errors; + private TextMeshProUGUI warning; + + + private void Start() + { + if (nCats) + CatManager.LaunchCats(); + else if (nyan) + CatManager.LaunchCat(); + + Canvas canvas = LoadingScreen.Instance.GetComponentInChildren(); + + status = CreateTextObject(canvas, "MMStatus"); + errors = CreateTextObject(canvas, "MMErrors"); + warning = CreateTextObject(canvas, "MMWarning"); + warning.text = ""; + + //if (Versioning.version_major == 1 && Versioning.version_minor == 0 && Versioning.Revision == 5 && Versioning.BuildID == 1024) + //{ + // warning.text = "Your KSP 1.0.5 is running on build 1024. You should upgrade to build 1028 to avoid problems with addons."; + // //if (GUI.Button(new Rect(Screen.width / 2f - 100, offsetY, 200, 20), "Click to open the Forum thread")) + // // Application.OpenURL("http://forum.kerbalspaceprogram.com/index.php?/topic/124998-silent-patch-for-ksp-105-published/"); + //} + } + + private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) + { + GameObject statusGameObject = new GameObject(name); + TextMeshProUGUI text = statusGameObject.AddComponent(); + text.text = "STATUS"; + text.fontSize = 16; + text.autoSizeTextContainer = true; + text.font = Resources.Load("Fonts/Calibri SDF", typeof(TMP_FontAsset)) as TMP_FontAsset; + text.alignment = TextAlignmentOptions.Center; + text.enableWordWrapping = false; + text.isOverlay = true; + statusGameObject.transform.SetParent(canvas.transform); + + return text; + } + + // Unsubscribe from events when the behavior dies + internal void OnDestroy() + { + GameEvents.onGUIRnDComplexSpawn.Remove(OnRnDCenterSpawn); + GameEvents.onGUIRnDComplexDespawn.Remove(OnRnDCenterDeSpawn); + } + + internal void Update() + { + if (GameSettings.MODIFIER_KEY.GetKey() && Input.GetKeyDown(KeyCode.F11) + && (HighLogic.LoadedScene == GameScenes.SPACECENTER || HighLogic.LoadedScene == GameScenes.MAINMENU) + && !inRnDCenter) + { + if (menu == null) + { + menu = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + new MultiOptionDialog( + "ModuleManagerMenu", + "", + "ModuleManager", + HighLogic.UISkin, + new Rect(0.5f, 0.5f, 150f, 60f), + new DialogGUIFlexibleSpace(), + new DialogGUIVerticalLayout( + new DialogGUIFlexibleSpace(), + new DialogGUIButton("Reload Database", + delegate + { + MMPatchLoader.keepPartDB = false; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Quick Reload Database", + delegate + { + MMPatchLoader.keepPartDB = true; + StartCoroutine(DataBaseReloadWithMM()); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Dump Database to Files", + delegate + { + StartCoroutine(DataBaseReloadWithMM(true)); + }, 140.0f, 30.0f, true), + new DialogGUIButton("Close", () => { }, 140.0f, 30.0f, true) + )), + false, + HighLogic.UISkin); + } + else + { + menu.Dismiss(); + menu = null; + } + } + + if (totalTime.IsRunning && HighLogic.LoadedScene == GameScenes.MAINMENU) + { + totalTime.Stop(); + log("Total loading Time = " + ((float)totalTime.ElapsedMilliseconds / 1000).ToString("F3") + "s"); + + Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND; + } + + float offsetY = Mathf.FloorToInt(0.23f * Screen.height); + float h; + if (warning) + { + warning.transform.localPosition = new Vector3(0, -offsetY); + h = warning.textBounds.size.y; + if (h > 0) + offsetY = offsetY + h + 10; + } + + if (status) + { + status.transform.localPosition = new Vector3(0, -offsetY); + status.text = MMPatchLoader.Instance.status; + + h = status.textBounds.size.y; + if (h > 0) + offsetY = offsetY + h + 10; + } + + if (errors) + { + errors.transform.localPosition = new Vector3(0, -offsetY); + errors.text = MMPatchLoader.Instance.errors; + } + + if (reloading) + { + float percent = 0; + if (!GameDatabase.Instance.IsReady()) + percent = GameDatabase.Instance.ProgressFraction(); + else if (!MMPatchLoader.Instance.IsReady()) + percent = 1f + MMPatchLoader.Instance.ProgressFraction(); + else if (!PartLoader.Instance.IsReady()) + percent = 2f + PartLoader.Instance.ProgressFraction(); + + int intPercent = Mathf.CeilToInt(percent * 100f / 3f); + ScreenMessages.PostScreenMessage("Database reloading " + intPercent + "%", Time.deltaTime, + ScreenMessageStyle.UPPER_CENTER); + } + } + + #region GUI stuff. + + internal static IntPtr intPtr = new IntPtr(long.MaxValue); + /* Not required anymore. At least + public static bool IsABadIdea() + { + return (intPtr.ToInt64() == long.MaxValue) && (Environment.OSVersion.Platform == PlatformID.Win32NT); + } + */ + + private IEnumerator DataBaseReloadWithMM(bool dump = false) + { + reloading = true; + + QualitySettings.vSyncCount = 0; + Application.targetFrameRate = -1; + + ScreenMessages.PostScreenMessage("Database reloading started", 1, ScreenMessageStyle.UPPER_CENTER); + yield return null; + + GameDatabase.Instance.Recompile = true; + GameDatabase.Instance.StartLoad(); + + // wait for it to finish + while (!GameDatabase.Instance.IsReady()) + yield return null; + + MMPatchLoader.Instance.StartLoad(); + + while (!MMPatchLoader.Instance.IsReady()) + yield return null; + + log("DB Reload OK with patchCount=" + MMPatchLoader.Instance.patchedNodeCount + " errorCount=" + + MMPatchLoader.Instance.errorCount + " needsUnsatisfiedCount=" + + MMPatchLoader.Instance.needsUnsatisfiedCount + " exceptionCount=" + MMPatchLoader.Instance.exceptionCount); + + PartResourceLibrary.Instance.LoadDefinitions(); + + PartUpgradeManager.Handler.FillUpgrades(); + + if (dump) + OutputAllConfigs(); + + PartLoader.Instance.StartLoad(); + + while (!PartLoader.Instance.IsReady()) + yield return null; + + // Needs more work. + //ConfigNode game = HighLogic.CurrentGame.config.GetNode("GAME"); + + //if (game != null && ResearchAndDevelopment.Instance != null) + //{ + // ScreenMessages.PostScreenMessage("GAME found"); + // ConfigNode scenario = game.GetNodes("SCENARIO").FirstOrDefault((ConfigNode n) => n.name == "ResearchAndDevelopment"); + // if (scenario != null) + // { + // ScreenMessages.PostScreenMessage("SCENARIO found"); + // ResearchAndDevelopment.Instance.OnLoad(scenario); + // } + //} + + QualitySettings.vSyncCount = GameSettings.SYNC_VBL; + Application.targetFrameRate = GameSettings.FRAMERATE_LIMIT; + reloading = false; + ScreenMessages.PostScreenMessage("Database reloading finished", 1, ScreenMessageStyle.UPPER_CENTER); + } + + private static void OutputAllConfigs() + { + string path = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "_MMCfgOutput" + + Path.DirectorySeparatorChar; + Directory.CreateDirectory(path); + + foreach (UrlDir.UrlConfig d in GameDatabase.Instance.root.AllConfigs) + { + string file = d.url.Replace('/', '.').Replace(':', '.'); + string filePath = path + file + ".cfg"; + try + { + + File.WriteAllText(filePath, d.config.ToString()); + } + catch (Exception e) + { + log("Exception while trying to write the file " + filePath + "\n" + e); + } + } + } + + #endregion GUI stuff. + + public bool ElectionAndCheck() + { + #region Type election + + // TODO : Move the old version check in a process that call Update. + + // Check for old version and MMSarbianExt + IEnumerable oldMM = + AssemblyLoader.loadedAssemblies.Where( + a => a.assembly.GetName().Name == Assembly.GetExecutingAssembly().GetName().Name) + .Where(a => a.assembly.GetName().Version.CompareTo(new System.Version(1, 5, 0)) == -1); + IEnumerable oldAssemblies = + oldMM.Concat(AssemblyLoader.loadedAssemblies.Where(a => a.assembly.GetName().Name == "MMSarbianExt")); + if (oldAssemblies.Any()) + { + IEnumerable badPaths = + oldAssemblies.Select(a => a.path) + .Select( + p => + Uri.UnescapeDataString( + new Uri(Path.GetFullPath(KSPUtil.ApplicationRootPath)).MakeRelativeUri(new Uri(p)) + .ToString() + .Replace('/', Path.DirectorySeparatorChar))); + string status = + "You have old versions of Module Manager (older than 1.5) or MMSarbianExt.\nYou will need to remove them for Module Manager and the mods using it to work\nExit KSP and delete those files :\n" + + String.Join("\n", badPaths.ToArray()); + PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "ModuleManagerOldVersions", "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); + log("Old version of Module Manager present. Stopping"); + return false; + } + + + //PopupDialog.SpawnPopupDialog(new Vector2(0.1f, 1f), new Vector2(0.2f, 1f), "Test of the dialog", "Stuff", "OK", false, UISkinManager.defaultSkin); + + Assembly currentAssembly = Assembly.GetExecutingAssembly(); + IEnumerable eligible = from a in AssemblyLoader.loadedAssemblies + let ass = a.assembly + where ass.GetName().Name == currentAssembly.GetName().Name + orderby ass.GetName().Version descending, a.path ascending + select a; + + // Elect the newest loaded version of MM to process all patch files. + // If there is a newer version loaded then don't do anything + // If there is a same version but earlier in the list, don't do anything either. + if (eligible.First().assembly != currentAssembly) + { + //loaded = true; + log("version " + currentAssembly.GetName().Version + " at " + currentAssembly.Location + + " lost the election"); + Destroy(gameObject); + return false; + } + string candidates = ""; + foreach (AssemblyLoader.LoadedAssembly a in eligible) + { + if (currentAssembly.Location != a.path) + candidates += "Version " + a.assembly.GetName().Version + " " + a.path + " " + "\n"; + } + if (candidates.Length > 0) + { + log("version " + currentAssembly.GetName().Version + " at " + currentAssembly.Location + + " won the election against\n" + candidates); + } + + #endregion Type election + + return true; + } + } +} diff --git a/ModuleManager.csproj b/ModuleManager.csproj index ed0b6bee..bd68697a 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -31,10 +31,12 @@ False - - - - + + + + + + From 1191841d6f532aa88c1b53441d1c88687d29fca2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 19 Aug 2017 14:08:08 -0700 Subject: [PATCH 032/342] Change debug C# version to default --- ModuleManager.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager.csproj b/ModuleManager.csproj index bd68697a..fec8d3e3 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -20,7 +20,7 @@ prompt 4 False - 5 + default none From d2fd007db31b1e99879e99db56db1a10ad6b43e7 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 26 Aug 2017 14:09:46 -0700 Subject: [PATCH 033/342] VS, why u do dis? --- .gitignore | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9791284a..365ba2a7 100644 --- a/.gitignore +++ b/.gitignore @@ -58,7 +58,7 @@ _ReSharper* *.ncrunch* .*crunch*.local.xml -# Installshield output folder +# Installshield output folder [Ee]xpress # DocProject is a documentation generator add-in @@ -110,4 +110,6 @@ UpgradeLog*.XML ModuleManager.csproj ModuleManager.csproj.user -ModuleManager.*.dll \ No newline at end of file +ModuleManager.*.dll + +.vs* From 536ff0cf0f06e0ae39b42920ce6189be99402d30 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 26 Aug 2017 14:11:12 -0700 Subject: [PATCH 034/342] Add ImmutableStack class --- Collections/ImmutableStack.cs | 37 +++++++++++++++++++++++++++++++++++ ModuleManager.csproj | 1 + 2 files changed, 38 insertions(+) create mode 100644 Collections/ImmutableStack.cs diff --git a/Collections/ImmutableStack.cs b/Collections/ImmutableStack.cs new file mode 100644 index 00000000..b865d9a9 --- /dev/null +++ b/Collections/ImmutableStack.cs @@ -0,0 +1,37 @@ +using System; + +namespace ModuleManager.Collections +{ + public class ImmutableStack + { + public readonly T value; + public readonly ImmutableStack parent; + + public ImmutableStack(T value) + { + this.value = value; + } + + private ImmutableStack(T value, ImmutableStack parent) + { + this.value = value; + this.parent = parent; + } + + public bool IsRoot => parent == null; + public ImmutableStack Root => IsRoot? this : parent.Root; + + public ImmutableStack Push(T newValue) + { + return new ImmutableStack(newValue, this); + } + + public ImmutableStack Pop() + { + if (IsRoot) throw new InvalidOperationException("Cannot pop from the root of a stack"); + return parent; + } + + public ImmutableStack ReplaceValue(T newValue) => new ImmutableStack(newValue, parent); + } +} diff --git a/ModuleManager.csproj b/ModuleManager.csproj index fec8d3e3..2f03e9ec 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -35,6 +35,7 @@ + From abba87ea8b94ea6cab2ab2a25828f7aed756fc8e Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 26 Aug 2017 20:06:55 -0700 Subject: [PATCH 035/342] Add PatchContext struct --- ModuleManager.csproj | 1 + PatchContext.cs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 PatchContext.cs diff --git a/ModuleManager.csproj b/ModuleManager.csproj index 2f03e9ec..865c4334 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -38,6 +38,7 @@ + diff --git a/PatchContext.cs b/PatchContext.cs new file mode 100644 index 00000000..ba9ea83f --- /dev/null +++ b/PatchContext.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ModuleManager +{ + public struct PatchContext + { + public readonly UrlDir.UrlConfig patchUrl; + public readonly UrlDir databaseRoot; + + public PatchContext(UrlDir.UrlConfig patchUrl, UrlDir databaseRoot) + { + this.patchUrl = patchUrl; + this.databaseRoot = databaseRoot; + } + } +} From 05342e2e8914bb5bb221db1ab80921554b006cad Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 26 Aug 2017 20:07:18 -0700 Subject: [PATCH 036/342] Use ImmutableStack and PatchContext in MM --- MMPatchLoader.cs | 150 ++++++++++++++++++++--------------------------- 1 file changed, 62 insertions(+), 88 deletions(-) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index 948edf13..7c8dd38b 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -11,6 +11,9 @@ using UnityEngine; using Debug = UnityEngine.Debug; +using ModuleManager.Collections; +using NodeStack = ModuleManager.Collections.ImmutableStack; + namespace ModuleManager { public delegate void ModuleManagerPostPatchCallback(); @@ -47,10 +50,6 @@ public class MMPatchLoader : LoadingSystem private static readonly Dictionary regexCache = new Dictionary(); - private static readonly Stack nodeStack = new Stack(); - - private static ConfigNode topNode; - private static string cachePath; internal static string techTreeFile; @@ -1133,6 +1132,7 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) try { + PatchContext context = new PatchContext(mod, GameDatabase.Instance.root); char[] sep = { '[', ']' }; string condition = ""; @@ -1157,17 +1157,16 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) if (url.type == type && WildcardMatch(url.name, pattern) && CheckConstraints(url.config, condition) && !IsPathInList(mod.url, excludePaths)) { - nodeStack.Clear(); switch (cmd) { case Command.Edit: log("Applying node " + mod.url + " to " + url.url); patchedNodeCount++; - url.config = ModifyNode(url.config, mod.config); + url.config = ModifyNode(new NodeStack(url.config), mod.config, context); break; case Command.Copy: - ConfigNode clone = ModifyNode(url.config, mod.config); + ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); if (url.config.name != mod.name) { log("Copying Node " + url.config.name + " into " + clone.name); @@ -1251,14 +1250,10 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) // ModifyNode applies the ConfigNode mod as a 'patch' to ConfigNode original, then returns the patched ConfigNode. // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse. - public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) + public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext context) { - ConfigNode newNode = DeepCopy(original); - - if (nodeStack.Count == 0) - topNode = newNode; - - nodeStack.Push(newNode); + ConfigNode newNode = DeepCopy(original.value); + NodeStack nodeStack = original.ReplaceValue(newNode); #region Values @@ -1285,7 +1280,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) valName = assignMatch.Groups[1].Value; - ConfigNode.Value val = RecurseVariableSearch(valName, mod); + ConfigNode.Value val = RecurseVariableSearch(valName, nodeStack.Push(mod), context); if (val == null) { @@ -1398,7 +1393,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) else { // Insert at the end by default - varValue = ProcessVariableSearch(modVal.value, newNode); + varValue = ProcessVariableSearch(modVal.value, nodeStack, context); if (varValue != null) InsertValue(newNode, match.Groups[2].Success ? index : int.MaxValue, valName, varValue); else @@ -1424,7 +1419,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) } else { - varValue = ProcessVariableSearch(modVal.value, newNode); + varValue = ProcessVariableSearch(modVal.value, nodeStack, context); if (varValue != null) { newNode.RemoveValues(valName); @@ -1447,7 +1442,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) while (index < valCount) { - varValue = ProcessVariableSearch(modVal.value, newNode); + varValue = ProcessVariableSearch(modVal.value, nodeStack, context); if (varValue != null) { @@ -1518,7 +1513,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) break; case Command.Rename: - if (nodeStack.Count == 1) + if (nodeStack.IsRoot) { log("Error - Renaming nodes does not work on top nodes"); errorCount++; @@ -1541,7 +1536,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) } else { - varValue = ProcessVariableSearch(modVal.value, newNode); + varValue = ProcessVariableSearch(modVal.value, nodeStack, context); if (varValue != null) { if (!newNode.HasValue(valName)) @@ -1585,7 +1580,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) if (command == Command.Insert) { ConfigNode newSubMod = new ConfigNode(subMod.name); - newSubMod = ModifyNode(newSubMod, subMod); + newSubMod = ModifyNode(nodeStack.Push(newSubMod), subMod, context); subName = newSubMod.name; int index; if (subName.Contains(",") && int.TryParse(subName.Split(',')[1], out index)) @@ -1613,7 +1608,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) //string newName = subName.Substring(0, start); //string path = subName.Substring(start + 1, end - start - 1); - ConfigNode toPaste = RecurseNodeSearch(subName.Substring(1), nodeStack.Peek()); + ConfigNode toPaste = RecurseNodeSearch(subName.Substring(1), nodeStack.Pop(), context); if (toPaste == null) { @@ -1623,7 +1618,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) } ConfigNode newSubMod = new ConfigNode(toPaste.name); - newSubMod = ModifyNode(newSubMod, toPaste); + newSubMod = ModifyNode(nodeStack.Push(newSubMod), toPaste, context); int index; if (subName.LastIndexOf(",") > 0 && int.TryParse(subName.Substring(subName.LastIndexOf(",") + 1), out index)) { @@ -1713,7 +1708,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) #if LOGSPAM msg += " Applying subnode " + subMod.name + "\n"; #endif - ConfigNode newSubNode = ModifyNode(subNodes[0], subMod); + ConfigNode newSubNode = ModifyNode(nodeStack.Push(subNodes[0]), subMod, context); subNodes[0].ClearData(); newSubNode.CopyTo(subNodes[0], newSubNode.name); } @@ -1729,7 +1724,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) if (nodeName != null) copy.AddValue("name", nodeName); - ConfigNode newSubNode = ModifyNode(copy, subMod); + ConfigNode newSubNode = ModifyNode(nodeStack.Push(copy), subMod, context); newNode.nodes.Add(newSubNode); } } @@ -1746,7 +1741,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) if (nodeName != null) copy.AddValue("name", nodeName); - ConfigNode newSubNode = ModifyNode(copy, subMod); + ConfigNode newSubNode = ModifyNode(nodeStack.Push(copy), subMod, context); newNode.nodes.Add(newSubNode); } } @@ -1769,7 +1764,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) case Command.Edit: // Edit in place - newSubNode = ModifyNode(subNode, subMod); + newSubNode = ModifyNode(nodeStack.Push(subNode), subMod, context); subNode.ClearData(); newSubNode.CopyTo(subNode, newSubNode.name); break; @@ -1783,7 +1778,7 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) case Command.Copy: // Copy the node - newSubNode = ModifyNode(subNode, subMod); + newSubNode = ModifyNode(nodeStack.Push(subNode), subMod, context); newNode.nodes.Add(newSubNode); break; } @@ -1797,21 +1792,18 @@ public ConfigNode ModifyNode(ConfigNode original, ConfigNode mod) #endregion Nodes - nodeStack.Pop(); - return newNode; } // Search for a ConfigNode by a path alike string - private ConfigNode RecurseNodeSearch(string path, ConfigNode currentNode) + private ConfigNode RecurseNodeSearch(string path, NodeStack nodeStack, PatchContext context) { //log("Path : \"" + path + "\""); if (path[0] == '/') { - currentNode = topNode; - path = path.Substring(1); + return RecurseNodeSearch(path.Substring(1), nodeStack.Root, context); } int nextSep = path.IndexOf('/'); @@ -1852,19 +1844,10 @@ private ConfigNode RecurseNodeSearch(string path, ConfigNode currentNode) // ../XXXXX if (path.StartsWith("../")) { - if (nodeStack.Count == 1) + if (nodeStack.IsRoot) return null; - ConfigNode result; - ConfigNode top = nodeStack.Pop(); - try - { - result = RecurseNodeSearch(path.Substring(3), nodeStack.Peek()); - } - finally - { - nodeStack.Push(top); - } - return result; + + return RecurseNodeSearch(path.Substring(3), nodeStack.Pop(), context); } //log("nextSep : \"" + nextSep + " \" root : \"" + root + " \" nodeType : \"" + nodeType + "\" nodeName : \"" + nodeName + "\""); @@ -1872,8 +1855,8 @@ private ConfigNode RecurseNodeSearch(string path, ConfigNode currentNode) // @XXXXX if (root) { - ConfigNode[] list = GameDatabase.Instance.GetConfigNodes(nodeType); - if (list.Length == 0) + IEnumerable urlConfigs = context.databaseRoot.GetConfigs(nodeType); + if (!urlConfigs.Any()) { log("Can't find nodeType:" + nodeType); return null; @@ -1881,15 +1864,15 @@ private ConfigNode RecurseNodeSearch(string path, ConfigNode currentNode) if (nodeName == null) { - currentNode = list[0]; + nodeStack = new NodeStack(urlConfigs.First().config); } else { - for (int i = 0; i < list.Length; i++) + foreach (UrlDir.UrlConfig url in urlConfigs) { - if (list[i].HasValue("name") && WildcardMatch(list[i].GetValue("name"), nodeName)) + if (url.config.HasValue("name") && WildcardMatch(url.config.GetValue("name"), nodeName)) { - currentNode = list[i]; + nodeStack = new NodeStack(url.config); break; } } @@ -1903,15 +1886,15 @@ private ConfigNode RecurseNodeSearch(string path, ConfigNode currentNode) ConfigNode last = null; while (true) { - ConfigNode n = FindConfigNodeIn(currentNode, nodeType, nodeName, index++); + ConfigNode n = FindConfigNodeIn(nodeStack.value, nodeType, nodeName, index++); if (n == last || n == null) { - currentNode = null; + nodeStack = null; break; } if (CheckConstraints(n, constraint)) { - currentNode = n; + nodeStack = nodeStack.Push(n); break; } last = n; @@ -1920,30 +1903,30 @@ private ConfigNode RecurseNodeSearch(string path, ConfigNode currentNode) else { // just get one node - currentNode = FindConfigNodeIn(currentNode, nodeType, nodeName, index); + nodeStack = nodeStack.Push(FindConfigNodeIn(nodeStack.value, nodeType, nodeName, index)); } } // XXXXXX/ - if (nextSep > 0 && currentNode != null) + if (nextSep > 0 && nodeStack != null) { path = path.Substring(nextSep + 1); //log("NewPath : \"" + path + "\""); - return RecurseNodeSearch(path, currentNode); + return RecurseNodeSearch(path, nodeStack, context); } - return currentNode; + return nodeStack.value; } // KeyName is group 1, index is group 2, value index is group 3, value separator is group 4 private static readonly Regex parseVarKey = new Regex(@"([\w\&\-\.]+)(?:,((?:[0-9]+)+))?(?:\[((?:[0-9]+)+)(?:,(.))?\])?"); // Search for a value by a path alike string - private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode currentNode) + private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nodeStack, PatchContext context) { //log("path:" + path); if (path[0] == '/') - return RecurseVariableSearch(path.Substring(1), topNode); + return RecurseVariableSearch(path.Substring(1), nodeStack.Root, context); int nextSep = path.IndexOf('/'); // make sure we don't stop on a ",/" which would be a value separator @@ -1958,7 +1941,7 @@ private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode cu string subName = path.Substring(1, nextSep - 1); string nodeType, nodeName; - ConfigNode target = null; + UrlDir.UrlConfig target = null; if (subName.Contains("[")) { @@ -1973,8 +1956,8 @@ private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode cu nodeName = string.Empty; } - ConfigNode[] list = GameDatabase.Instance.GetConfigNodes(nodeType); - if (list.Length == 0) + IEnumerable urlConfigs = context.databaseRoot.GetConfigs(nodeType); + if (!urlConfigs.Any()) { log("Can't find nodeType:" + nodeType); return null; @@ -1982,36 +1965,27 @@ private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode cu if (nodeName == string.Empty) { - target = list[0]; + target = urlConfigs.First(); } else { - for (int i = 0; i < list.Length; i++) + foreach (UrlDir.UrlConfig url in urlConfigs) { - if (list[i].HasValue("name") && WildcardMatch(list[i].GetValue("name"), nodeName)) + if (url.config.HasValue("name") && WildcardMatch(url.config.GetValue("name"), nodeName)) { - target = list[i]; + target = url; break; } } } - return target != null ? RecurseVariableSearch(path.Substring(nextSep + 1), target) : null; + return target != null ? RecurseVariableSearch(path.Substring(nextSep + 1), new NodeStack(target.config), context) : null; } if (path.StartsWith("../")) { - if (nodeStack.Count == 1) + if (nodeStack.IsRoot) return null; - ConfigNode.Value result; - ConfigNode top = nodeStack.Pop(); - try - { - result = RecurseVariableSearch(path.Substring(3), nodeStack.Peek()); - } - finally - { - nodeStack.Push(top); - } - return result; + + return RecurseVariableSearch(path.Substring(3), nodeStack.Pop(), context); } // Node search @@ -2057,11 +2031,11 @@ private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode cu ConfigNode last = null; while (true) { - ConfigNode n = FindConfigNodeIn(currentNode, nodeType, nodeName, index++); + ConfigNode n = FindConfigNodeIn(nodeStack.value, nodeType, nodeName, index++); if (n == last || n == null) break; if (CheckConstraints(n, constraint)) - return RecurseVariableSearch(path.Substring(nextSep + 1), n); + return RecurseVariableSearch(path.Substring(nextSep + 1), nodeStack.Push(n), context); last = n; } return null; @@ -2069,9 +2043,9 @@ private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode cu else { // just get one node - ConfigNode n = FindConfigNodeIn(currentNode, nodeType, nodeName, index); + ConfigNode n = FindConfigNodeIn(nodeStack.value, nodeType, nodeName, index); if (n != null) - return RecurseVariableSearch(path.Substring(nextSep + 1), n); + return RecurseVariableSearch(path.Substring(nextSep + 1), nodeStack.Push(n), context); return null; } } @@ -2091,10 +2065,10 @@ private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode cu if (match.Groups[2].Success) int.TryParse(match.Groups[2].Value, out idx); - ConfigNode.Value cVal = FindValueIn(currentNode, valName, idx); + ConfigNode.Value cVal = FindValueIn(nodeStack.value, valName, idx); if (cVal == null) { - log("Cannot find key " + valName + " in " + currentNode.name); + log("Cannot find key " + valName + " in " + nodeStack.value.name); return null; } @@ -2117,7 +2091,7 @@ private static ConfigNode.Value RecurseVariableSearch(string path, ConfigNode cu return cVal; } - private static string ProcessVariableSearch(string value, ConfigNode node) + private static string ProcessVariableSearch(string value, NodeStack nodeStack, PatchContext context) { // value = #xxxx$yyyyy$zzzzz$aaaa$bbbb // There is 2 or more '$' @@ -2134,7 +2108,7 @@ private static string ProcessVariableSearch(string value, ConfigNode node) for (int i = 1; i < split.Length - 1; i = i + 2) { - ConfigNode.Value result = RecurseVariableSearch(split[i], node); + ConfigNode.Value result = RecurseVariableSearch(split[i], nodeStack, context); if (result == null || result.value == null) return null; builder.Append(result.value); From 8685da977f63049acc2ab326cfb89e8c189049ff Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 26 Aug 2017 23:34:14 -0700 Subject: [PATCH 037/342] Remove unused code Apparently had to do with texture replacer corruption, but not called anywhere --- MMPatchLoader.cs | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index 7c8dd38b..b306a916 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -535,37 +535,6 @@ private void SaveModdedPhysics() configs[0].config.Save(physicsPath); } - - // DB check used to track the now fixed TextureReplacer corruption - public static void checkValues() - { - foreach (UrlDir.UrlConfig mod in GameDatabase.Instance.root.AllConfigs) - { - if (checkValues(mod.config)) - { - log("Found bad value"); - return; - } - } - log("Found no bad value"); - } - - static bool checkValues(ConfigNode node) - { - foreach (ConfigNode.Value value in node.values) - { - if (value.name.Length == -1) - return true; - } - - foreach (ConfigNode subNode in node.nodes) - { - if (checkValues(subNode)) - return true; - } - return false; - } - private string FileSHA(string filename) { try From 038db2345ec22b9592e1510b850a0f52080baac4 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 27 Aug 2017 12:57:14 -0700 Subject: [PATCH 038/342] Implement IEnumerable --- Collections/ImmutableStack.cs | 45 ++++++++++++++++++++++++++++++- Extensions/NodeStackExtensions.cs | 26 ++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 Extensions/NodeStackExtensions.cs diff --git a/Collections/ImmutableStack.cs b/Collections/ImmutableStack.cs index b865d9a9..554c696e 100644 --- a/Collections/ImmutableStack.cs +++ b/Collections/ImmutableStack.cs @@ -1,9 +1,48 @@ using System; +using System.Collections; +using System.Collections.Generic; namespace ModuleManager.Collections { - public class ImmutableStack + public class ImmutableStack : IEnumerable { + public struct Enumerator : IEnumerator + { + private ImmutableStack head; + private ImmutableStack currentStack; + + public Enumerator(ImmutableStack stack) + { + head = stack; + currentStack = null; + } + + public T Current => currentStack.value; + object IEnumerator.Current => Current; + + public void Dispose() { } + + public bool MoveNext() + { + if (currentStack == null) + { + currentStack = head; + return true; + } + else if (!currentStack.IsRoot) + { + currentStack = currentStack.parent; + return true; + } + else + { + return false; + } + } + + public void Reset() => currentStack = null; + } + public readonly T value; public readonly ImmutableStack parent; @@ -33,5 +72,9 @@ public ImmutableStack Pop() } public ImmutableStack ReplaceValue(T newValue) => new ImmutableStack(newValue, parent); + + public Enumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/Extensions/NodeStackExtensions.cs b/Extensions/NodeStackExtensions.cs new file mode 100644 index 00000000..13e81cf7 --- /dev/null +++ b/Extensions/NodeStackExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NodeStack = ModuleManager.Collections.ImmutableStack; + +namespace ModuleManager.Extensions +{ + public static class NodeStackExtensions + { + public static string GetPath(this NodeStack stack) + { + int length = stack.Sum(node => node.name.Length) + stack.Depth - 1; + StringBuilder sb = new StringBuilder(length); + + foreach (ConfigNode node in stack) + { + string nodeName = node.name; + sb.Insert(0, node.name); + if (sb.Length < sb.Capacity) sb.Insert(0, '/'); + } + + return sb.ToString(); + } + } +} From 946194e0bb9b1d8aca95ae0db677bcc84315b69a Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 27 Aug 2017 12:57:26 -0700 Subject: [PATCH 039/342] Add Depth property --- Collections/ImmutableStack.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Collections/ImmutableStack.cs b/Collections/ImmutableStack.cs index 554c696e..5d181567 100644 --- a/Collections/ImmutableStack.cs +++ b/Collections/ImmutableStack.cs @@ -60,6 +60,8 @@ private ImmutableStack(T value, ImmutableStack parent) public bool IsRoot => parent == null; public ImmutableStack Root => IsRoot? this : parent.Root; + public int Depth => IsRoot ? 1 : parent.Depth + 1; + public ImmutableStack Push(T newValue) { return new ImmutableStack(newValue, this); From 475aa6627978100bb3cf0aa24eacefea4779b095 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 27 Aug 2017 13:03:12 -0700 Subject: [PATCH 040/342] Use immutability in CheckNeeds --- MMPatchLoader.cs | 131 ++++++++++++++++++++++------------------------- 1 file changed, 62 insertions(+), 69 deletions(-) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index b306a916..99462705 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -892,7 +892,7 @@ private void CheckNeeds(List excludePaths) } // Recursively check the contents - CheckNeeds(currentMod.config, currentMod.parent.url, new List()); + CheckNeeds(new NodeStack(mod.config), new PatchContext(mod, GameDatabase.Instance.root)); } catch (Exception ex) { @@ -903,91 +903,84 @@ private void CheckNeeds(List excludePaths) } } - private void CheckNeeds(ConfigNode subMod, string url, List path) + private void CheckNeeds(NodeStack stack, PatchContext context) { - try + bool needsCopy = false; + ConfigNode original = stack.value; + ConfigNode copy = new ConfigNode(original.name); + for (int i = 0; i < original.values.Count; ++i) { - path.Add(subMod.name); - bool needsCopy = false; - ConfigNode copy = new ConfigNode(subMod.name); - for (int i = 0; i < subMod.values.Count; ++i) + ConfigNode.Value val = original.values[i]; + string valname = val.name; + try { - ConfigNode.Value val = subMod.values[i]; - string valname = val.name; - try + if (CheckNeeds(ref valname)) { - if (CheckNeeds(ref valname)) - { - copy.AddValue(valname, val.value); - } - else - { - needsCopy = true; - log( - "Deleting value in file: " + url + " subnode: " + string.Join("/", path.ToArray()) + - " value: " + val.name + " = " + val.value + " as it can't satisfy its NEEDS"); - needsUnsatisfiedCount++; - } + copy.AddValue(valname, val.value); } - catch (ArgumentOutOfRangeException e) - { - log("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"\n" + e); - throw; - } - catch (Exception e) + else { - log("General Exception " + e.GetType().Name + " for value \"" + val.name + " = " + val.value + "\"\n" + e.ToString()); - throw; + needsCopy = true; + log( + "Deleting value in file: " + context.patchUrl.url + " subnode: " + stack.GetPath() + + " value: " + val.name + " = " + val.value + " as it can't satisfy its NEEDS"); + needsUnsatisfiedCount++; } } - - for (int i = 0; i < subMod.nodes.Count; ++i) + catch (ArgumentOutOfRangeException e) { - ConfigNode node = subMod.nodes[i]; - string nodeName = node.name; + log("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"\n" + e); + throw; + } + catch (Exception e) + { + log("General Exception " + e.GetType().Name + " for value \"" + val.name + " = " + val.value + "\"\n" + e.ToString()); + throw; + } + } - if (nodeName == null) - { - log("Error - Node in file " + url + " subnode: " + string.Join("/", path.ToArray()) + - " has config.name == null"); - } + for (int i = 0; i < original.nodes.Count; ++i) + { + ConfigNode node = original.nodes[i]; + string nodeName = node.name; - try - { - if (CheckNeeds(ref nodeName)) - { - node.name = nodeName; - CheckNeeds(node, url, path); - copy.AddNode(node); - } - else - { - needsCopy = true; - log( - "Deleting node in file: " + url + " subnode: " + string.Join("/", path.ToArray()) + "/" + - node.name + " as it can't satisfy its NEEDS"); - needsUnsatisfiedCount++; - } - } - catch (ArgumentOutOfRangeException e) + if (nodeName == null) + { + log("Error - Node in file " + context.patchUrl.url + " subnode: " + stack.GetPath() + + " has config.name == null"); + } + + try + { + if (CheckNeeds(ref nodeName)) { - log("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"\n" + e); - throw; + node.name = nodeName; + CheckNeeds(stack.Push(node), context); + copy.AddNode(node); } - catch (Exception e) + else { - log("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"\n " + e.ToString()); - throw; + needsCopy = true; + log( + "Deleting node in file: " + context.patchUrl.url + " subnode: " + stack.GetPath() + "/" + + node.name + " as it can't satisfy its NEEDS"); + needsUnsatisfiedCount++; } } - - if (needsCopy) - ShallowCopy(copy, subMod); - } - finally - { - path.RemoveAt(path.Count - 1); + catch (ArgumentOutOfRangeException e) + { + log("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"\n" + e); + throw; + } + catch (Exception e) + { + log("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"\n " + e.ToString()); + throw; + } } + + if (needsCopy) + ShallowCopy(copy, original); } /// From 27f788f0c06835ee965c6dae157fa97e95c0dd47 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 27 Aug 2017 13:26:46 -0700 Subject: [PATCH 041/342] Forgot a using directive --- MMPatchLoader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index 99462705..ad7a38b2 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -12,6 +12,7 @@ using Debug = UnityEngine.Debug; using ModuleManager.Collections; +using ModuleManager.Extensions; using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager From 04df68375017aabfe67e589e050325357463952c Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 27 Aug 2017 13:29:30 -0700 Subject: [PATCH 042/342] Ged rid of Win64 specific code Doesn't matter anymore --- MMPatchLoader.cs | 74 +++++++++--------------------------------------- 1 file changed, 14 insertions(+), 60 deletions(-) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index ad7a38b2..7b72e032 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -35,8 +35,6 @@ public class MMPatchLoader : LoadingSystem public int needsUnsatisfiedCount = 0; - private int catEatenCount = 0; - private Dictionary errorFiles; private List mods; @@ -160,31 +158,8 @@ public static void addPostPatchCallback(ModuleManagerPostPatchCallback callback) if (!postPatchCallbacks.Contains(callback)) postPatchCallbacks.Add(callback); } - private List PrePatchInit() + private void PrePatchInit() { - #region Excluding directories - - // Build a list of subdirectory that won't be processed - List excludePaths = new List(); - - //if (ModuleManager.IsABadIdea()) - //{ - // foreach (UrlDir.UrlConfig mod in GameDatabase.Instance.root.AllConfigs) - // { - // if (mod.name == "MODULEMANAGER[NOWIN64]") - // { - // string fullpath = mod.url.Substring(0, mod.url.LastIndexOf('/')); - // string excludepath = fullpath.Substring(0, fullpath.LastIndexOf('/')); - // excludePaths.Add(excludepath); - // log("excludepath: " + excludepath); - // } - // } - // if (excludePaths.Any()) - // log("will not process patches in these subdirectories since they were disbaled on KSP Win64:\n" + String.Join("\n", excludePaths.ToArray())); - //} - - #endregion Excluding directories - #region List of mods //string envInfo = "ModuleManager env info\n"; @@ -275,8 +250,6 @@ private List PrePatchInit() mods.Sort(); - return excludePaths; - #endregion List of mods } @@ -318,7 +291,7 @@ private IEnumerator ProcessPatch(bool blocking) log(status); yield return null; - List excludePaths = PrePatchInit(); + PrePatchInit(); if (!useCache) @@ -339,7 +312,7 @@ private IEnumerator ProcessPatch(bool blocking) status = "Checking NEEDS."; log(status); yield return null; - CheckNeeds(excludePaths); + CheckNeeds(); #endregion Check Needs @@ -351,23 +324,23 @@ private IEnumerator ProcessPatch(bool blocking) yield return null; // :First node - yield return StartCoroutine(ApplyPatch(excludePaths, ":FIRST"), blocking); + yield return StartCoroutine(ApplyPatch(":FIRST"), blocking); // any node without a :pass - yield return StartCoroutine(ApplyPatch(excludePaths, ":LEGACY"), blocking); + yield return StartCoroutine(ApplyPatch(":LEGACY"), blocking); foreach (string mod in mods) { string upperModName = mod.ToUpper(); - yield return StartCoroutine(ApplyPatch(excludePaths, ":BEFORE[" + upperModName + "]"), blocking); - yield return StartCoroutine(ApplyPatch(excludePaths, ":FOR[" + upperModName + "]"), blocking); - yield return StartCoroutine(ApplyPatch(excludePaths, ":AFTER[" + upperModName + "]"), blocking); + yield return StartCoroutine(ApplyPatch(":BEFORE[" + upperModName + "]"), blocking); + yield return StartCoroutine(ApplyPatch(":FOR[" + upperModName + "]"), blocking); + yield return StartCoroutine(ApplyPatch(":AFTER[" + upperModName + "]"), blocking); } // :Final node - yield return StartCoroutine(ApplyPatch(excludePaths, ":FINAL"), blocking); + yield return StartCoroutine(ApplyPatch(":FINAL"), blocking); - PurgeUnused(excludePaths); + PurgeUnused(); #endregion Applying patches @@ -705,8 +678,6 @@ private void CreateCache() cache.AddValue("patchedNodeCount", patchedNodeCount.ToString()); - cache.AddValue("catEatenCount", catEatenCount.ToString()); - foreach (UrlDir.UrlConfig config in GameDatabase.Instance.root.AllConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); @@ -801,10 +772,6 @@ private void LoadCache() if (cache.HasValue("patchedNodeCount")) int.TryParse(cache.GetValue("patchedNodeCount"), out patchedNodeCount); - if (cache.HasValue("catEatenCount")) - int.TryParse(cache.GetValue("catEatenCount"), out catEatenCount); - - // Create the fake file where we load the physic config cache UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); // need to use a file with a cfg extension to get the right fileType or you can't AddConfig on it @@ -840,14 +807,11 @@ private void StatusUpdate() if (exceptionCount > 0) status += ", encountered " + exceptionCount + " exception" + (exceptionCount != 1 ? "s" : "") + ""; - - if (catEatenCount > 0) - status += ", " + catEatenCount + " patch" + (catEatenCount != 1 ? "es were" : " was") + " eaten by the Win64 cat"; } #region Needs checking - private void CheckNeeds(List excludePaths) + private void CheckNeeds() { UrlDir.UrlConfig[] allConfigs = GameDatabase.Instance.root.AllConfigs.ToArray(); @@ -857,16 +821,6 @@ private void CheckNeeds(List excludePaths) UrlDir.UrlConfig currentMod = mod; try { - string name; - if (IsPathInList(currentMod.url, excludePaths) && (ParseCommand(currentMod.type, out name) != Command.Insert)) - { - mod.parent.configs.Remove(currentMod); - catEatenCount++; - log("Deleting Node in file " + currentMod.parent.url + " subnode: " + currentMod.type + - " as it is set to be disabled on KSP Win64"); - continue; - } - if (mod.config.name == null) { log("Error - Node in file " + currentMod.parent.url + " subnode: " + currentMod.type + @@ -1026,7 +980,7 @@ private bool CheckNeeds(ref string name) return true; } - private void PurgeUnused(List excludePaths) + private void PurgeUnused() { foreach (UrlDir.UrlConfig mod in GameDatabase.Instance.root.AllConfigs.ToArray()) { @@ -1042,7 +996,7 @@ private void PurgeUnused(List excludePaths) #region Applying Patches // Apply patch to all relevent nodes - public IEnumerator ApplyPatch(List excludePaths, string Stage) + public IEnumerator ApplyPatch(string Stage) { StatusUpdate(); log(Stage + (Stage == ":LEGACY" ? " (default) pass" : " pass")); @@ -1118,7 +1072,7 @@ public IEnumerator ApplyPatch(List excludePaths, string Stage) do { if (url.type == type && WildcardMatch(url.name, pattern) - && CheckConstraints(url.config, condition) && !IsPathInList(mod.url, excludePaths)) + && CheckConstraints(url.config, condition)) { switch (cmd) { From 340d113a0ab871b9df635e07db5d974dad458092 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 28 Aug 2017 22:55:42 -0700 Subject: [PATCH 043/342] Needs to be included in the project too --- ModuleManager.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/ModuleManager.csproj b/ModuleManager.csproj index 865c4334..28c6519d 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -36,6 +36,7 @@ + From b1a88634ddaf8a68bb0545cb062714096ad54c65 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 28 Aug 2017 22:58:13 -0700 Subject: [PATCH 044/342] Add logging interface --- Logging/IBasicLogger.cs | 15 +++++++++++++++ Logging/ModLogger.cs | 29 +++++++++++++++++++++++++++++ ModuleManager.csproj | 2 ++ 3 files changed, 46 insertions(+) create mode 100644 Logging/IBasicLogger.cs create mode 100644 Logging/ModLogger.cs diff --git a/Logging/IBasicLogger.cs b/Logging/IBasicLogger.cs new file mode 100644 index 00000000..77cfbdf4 --- /dev/null +++ b/Logging/IBasicLogger.cs @@ -0,0 +1,15 @@ +using System; +using UnityEngine; + +namespace ModuleManager.Logging +{ + // Stripped down version of UnityEngine.ILogger + public interface IBasicLogger + { + void Log(LogType logType, string message); + void Info(string message); + void Warning(string message); + void Error(string message); + void Exception(string message, Exception exception); + } +} diff --git a/Logging/ModLogger.cs b/Logging/ModLogger.cs new file mode 100644 index 00000000..897eec33 --- /dev/null +++ b/Logging/ModLogger.cs @@ -0,0 +1,29 @@ +using System; +using UnityEngine; + +namespace ModuleManager.Logging +{ + public class ModLogger : IBasicLogger + { + private string prefix; + private ILogger logger; + + public ModLogger(string prefix, ILogger logger) + { + this.prefix = "[" + prefix + "] "; + this.logger = logger; + } + + public void Log(LogType logType, string message) => logger.Log(logType, prefix + message); + + public void Info(string message) => Log(LogType.Log, message); + public void Warning(string message) => Log(LogType.Warning, message); + public void Error(string message) => Log(LogType.Error, message); + + public void Exception(string message, Exception exception) + { + Error(message); + logger.LogException(exception); + } + } +} diff --git a/ModuleManager.csproj b/ModuleManager.csproj index 28c6519d..01e02301 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -37,6 +37,8 @@ + + From ad61311dcc5cd6c9ed2973e6470a9dc2a4797e1f Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 28 Aug 2017 23:31:54 -0700 Subject: [PATCH 045/342] Extract progress into its own object --- IPatchProgress.cs | 28 +++++++++++ ModuleManager.csproj | 2 + PatchProgress.cs | 109 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 IPatchProgress.cs create mode 100644 PatchProgress.cs diff --git a/IPatchProgress.cs b/IPatchProgress.cs new file mode 100644 index 00000000..e3c0a067 --- /dev/null +++ b/IPatchProgress.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace ModuleManager +{ + public interface IPatchProgress + { + int AppliedPatchCount { get; } + int ErrorCount { get; } + int ExceptionCount { get; } + int NeedsUnsatisfiedCount { get; } + int PatchedNodeCount { get; set; } + float ProgressFraction { get; } + int TotalPatchCount { get; } + Dictionary ErrorFiles { get; } + + void Error(UrlDir.UrlConfig url, string message); + void Exception(string message, Exception exception); + void Exception(UrlDir.UrlConfig url, string message, Exception exception); + void NeedsUnsatisfiedNode(string url, string path); + void NeedsUnsatisfiedValue(string url, string path, string valName); + void NodeCopied(string url, string patchUrl); + void NodeDeleted(string url, string patchUrl); + void NodePatched(string url, string patchUrl); + void PatchAdded(); + void PatchApplied(); + } +} diff --git a/ModuleManager.csproj b/ModuleManager.csproj index 01e02301..450001fa 100644 --- a/ModuleManager.csproj +++ b/ModuleManager.csproj @@ -37,11 +37,13 @@ + + diff --git a/PatchProgress.cs b/PatchProgress.cs new file mode 100644 index 00000000..64e02d03 --- /dev/null +++ b/PatchProgress.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using ModuleManager.Logging; + +namespace ModuleManager +{ + public class PatchProgress : IPatchProgress + { + public int TotalPatchCount { get; private set; } = 0; + + public int AppliedPatchCount { get; private set; } = 0; + + public int PatchedNodeCount { get; set; } = 0; + + public int ErrorCount { get; private set; } = 0; + + public int ExceptionCount { get; private set; } = 0; + + public int NeedsUnsatisfiedCount { get; private set; } = 0; + + public Dictionary ErrorFiles { get; } = new Dictionary(); + + private IBasicLogger logger; + + public float ProgressFraction + { + get + { + if (TotalPatchCount > 0) + return (AppliedPatchCount + NeedsUnsatisfiedCount) / (float)TotalPatchCount; + return 0; + } + } + + public PatchProgress(IBasicLogger logger) + { + this.logger = logger; + } + + public void PatchAdded() + { + TotalPatchCount += 1; + } + + public void NodePatched(string url, string patchUrl) + { + logger.Info($"Applying node {patchUrl} to {url}"); + PatchedNodeCount += 1; + } + + public void NodeCopied(string url, string patchUrl) + { + logger.Info($"Copying Node {url} using {patchUrl}"); + } + + public void NodeDeleted(string url, string patchUrl) + { + logger.Info($"Deleting Node {url} using {patchUrl}"); + } + + public void PatchApplied() + { + AppliedPatchCount += 1; + } + + public void NeedsUnsatisfiedNode(string url, string path) + { + logger.Info($"Deleting Node in file {url} subnode: {path} as it can't satisfy its NEEDS"); + NeedsUnsatisfiedCount += 1; + } + + public void NeedsUnsatisfiedValue(string url, string path, string valName) + { + logger.Info($"Deleting value in file {url} subnode: {path} value: {valName} as it can't satisfy its NEEDS"); + NeedsUnsatisfiedCount += 1; + } + + public void Error(UrlDir.UrlConfig url, string message) + { + ErrorCount += 1; + logger.Error(message); + RecordErrorFile(url); + } + + public void Exception(string message, Exception exception) + { + ExceptionCount += 1; + logger.Exception(message, exception); + } + + public void Exception(UrlDir.UrlConfig url, string message, Exception exception) + { + Exception(message, exception); + RecordErrorFile(url); + } + + private void RecordErrorFile(UrlDir.UrlConfig url) + { + string key = url.parent.url + "." + url.parent.fileExtension; + if (key[0] == '/') + key = key.Substring(1); + + if (ErrorFiles.ContainsKey(key)) + ErrorFiles[key] += 1; + else + ErrorFiles[key] = 1; + } + } +} From 76c998c61895de68f63f79fc57d0f64b30069d40 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 28 Aug 2017 23:36:24 -0700 Subject: [PATCH 046/342] Use logger and progress Make some things static that no longer depend on the patch loader's state --- MMPatchLoader.cs | 345 ++++++++++++++++++----------------------------- ModuleManager.cs | 6 +- PatchContext.cs | 10 +- 3 files changed, 143 insertions(+), 218 deletions(-) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index 7b72e032..91494d46 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -11,7 +11,7 @@ using UnityEngine; using Debug = UnityEngine.Debug; -using ModuleManager.Collections; +using ModuleManager.Logging; using ModuleManager.Extensions; using NodeStack = ModuleManager.Collections.ImmutableStack; @@ -23,19 +23,6 @@ namespace ModuleManager [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public class MMPatchLoader : LoadingSystem { - public int totalPatchCount = 0; - - public int appliedPatchCount = 0; - - public int patchedNodeCount = 0; - - public int errorCount = 0; - - public int exceptionCount = 0; - - public int needsUnsatisfiedCount = 0; - - private Dictionary errorFiles; private List mods; @@ -75,6 +62,10 @@ public class MMPatchLoader : LoadingSystem private const float yieldInterval = 1f/30f; // Patch at ~30fps + private IBasicLogger logger; + + public IPatchProgress progress; + public static MMPatchLoader Instance { get; private set; } private void Awake() @@ -95,6 +86,9 @@ private void Awake() defaultPhysicsPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "Physics.cfg"; partDatabasePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "PartDatabase.cfg"; shaPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "GameData" + Path.DirectorySeparatorChar + "ModuleManager.ConfigSHA"; + + logger = new ModLogger("ModuleManager", Debug.logger); + progress = new PatchProgress(logger); } private bool ready; @@ -105,17 +99,12 @@ public override bool IsReady() if (ready) { patchSw.Stop(); - log("Ran in " + ((float)patchSw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); + logger.Info("Ran in " + ((float)patchSw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); } return ready; } - public override float ProgressFraction() - { - if (totalPatchCount > 0) - return (appliedPatchCount + needsUnsatisfiedCount) / (float)totalPatchCount; - return 0; - } + public override float ProgressFraction() => progress.ProgressFraction; public override string ProgressTitle() { @@ -129,7 +118,7 @@ public override void StartLoad() public void Update() { - if (appliedPatchCount > 0 && HighLogic.LoadedScene == GameScenes.LOADING) + if (progress.AppliedPatchCount > 0 && HighLogic.LoadedScene == GameScenes.LOADING) StatusUpdate(); } @@ -138,12 +127,7 @@ public void StartLoad(bool blocking) patchSw.Reset(); patchSw.Start(); - totalPatchCount = 0; - appliedPatchCount = 0; - patchedNodeCount = 0; - errorCount = 0; - needsUnsatisfiedCount = 0; - errorFiles = new Dictionary(); + progress = new PatchProgress(logger); ready = false; @@ -208,7 +192,7 @@ private void PrePatchInit() string name; if (ParseCommand(cfgmod.type, out name) != Command.Insert) { - totalPatchCount++; + progress.PatchAdded(); if (name.Contains(":FOR[")) { name = RemoveWS(name); @@ -227,9 +211,8 @@ private void PrePatchInit() } catch (ArgumentOutOfRangeException) { - log("Skipping :FOR init for line " + name + + progress.Error(cfgmod, "Skipping :FOR init for line " + name + ". The line most likely contains a space that should be removed"); - errorCount++; } } } @@ -246,7 +229,7 @@ private void PrePatchInit() modlist += " " + cleanName + "\n"; } } - log(modlist); + logger.Info(modlist); mods.Sort(); @@ -270,7 +253,7 @@ Coroutine StartCoroutine(IEnumerator enumerator, bool blocking) private IEnumerator ProcessPatch(bool blocking) { status = "Checking Cache"; - log(status); + logger.Info(status); yield return null; try @@ -279,7 +262,7 @@ private IEnumerator ProcessPatch(bool blocking) } catch (Exception ex) { - log("Exception in IsCacheUpToDate : " + ex.Message + "\n" + ex.StackTrace); + logger.Exception("Exception in IsCacheUpToDate", ex); useCache = false; } @@ -288,7 +271,7 @@ private IEnumerator ProcessPatch(bool blocking) #endif status = "Pre patch init"; - log(status); + logger.Info(status); yield return null; PrePatchInit(); @@ -310,7 +293,7 @@ private IEnumerator ProcessPatch(bool blocking) // Do filtering with NEEDS status = "Checking NEEDS."; - log(status); + logger.Info(status); yield return null; CheckNeeds(); @@ -319,7 +302,7 @@ private IEnumerator ProcessPatch(bool blocking) #region Applying patches status = "Applying patches"; - log(status); + logger.Info(status); yield return null; @@ -346,15 +329,15 @@ private IEnumerator ProcessPatch(bool blocking) #region Logging - if (errorCount > 0 || exceptionCount > 0) + if (progress.ErrorCount > 0 || progress.ExceptionCount > 0) { - foreach (string file in errorFiles.Keys) + foreach (string file in progress.ErrorFiles.Keys) { - errors += errorFiles[file] + " error" + (errorFiles[file] > 1 ? "s" : "") + " related to GameData/" + file + errors += progress.ErrorFiles[file] + " error" + (progress.ErrorFiles[file] > 1 ? "s" : "") + " related to GameData/" + file + "\n"; } - - log("Errors in patch prevents the creation of the cache"); + + logger.Warning("Errors in patch prevents the creation of the cache"); try { if (File.Exists(cachePath)) @@ -364,13 +347,13 @@ private IEnumerator ProcessPatch(bool blocking) } catch (Exception e) { - log("Exception while deleting stale cache " + e); + logger.Exception("Exception while deleting stale cache ", e); } } else { status = "Saving Cache"; - log(status); + logger.Info(status); yield return null; CreateCache(); } @@ -381,14 +364,14 @@ private IEnumerator ProcessPatch(bool blocking) else { status = "Loading from Cache"; - log(status); + logger.Info(status); yield return null; LoadCache(); } StatusUpdate(); - log(status + "\n" + errors); + logger.Info(status + "\n" + errors); #endregion Logging @@ -397,13 +380,13 @@ private IEnumerator ProcessPatch(bool blocking) #endif // TODO : Remove if we ever get a way to load sooner - log("Reloading resources definitions"); + logger.Info("Reloading resources definitions"); PartResourceLibrary.Instance.LoadDefinitions(); - log("Reloading Trait configs"); + logger.Info("Reloading Trait configs"); GameDatabase.Instance.ExperienceConfigs.LoadTraitConfigs(); - - log("Reloading Part Upgrades"); + + logger.Info("Reloading Part Upgrades"); PartUpgradeManager.Handler.FillUpgrades(); foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks) @@ -414,7 +397,7 @@ private IEnumerator ProcessPatch(bool blocking) } catch (Exception e) { - log("Exception while running a post patch callback\n" + e); + logger.Exception("Exception while running a post patch callback", e); } yield return null; } @@ -433,19 +416,19 @@ private IEnumerator ProcessPatch(bool blocking) { try { - log("Calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()"); + logger.Info("Calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()"); method.Invoke(null, null); } catch (Exception e) { - log("Exception while calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "() :\n" + e); + logger.Exception("Exception while calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()", e); } } } } catch (Exception e) { - log("Post run call threw an exception in loading " + ass.FullName + ": " + e); + logger.Exception("Post run call threw an exception in loading " + ass.FullName, e); } } @@ -460,12 +443,12 @@ private IEnumerator ProcessPatch(bool blocking) { try { - log("Calling " + obj.GetType().Name + "." + method.Name + "()"); + logger.Info("Calling " + obj.GetType().Name + "." + method.Name + "()"); method.Invoke(obj, null); } catch (Exception e) { - log("Exception while calling " + obj.GetType().Name + "." + method.Name + "() :\n" + e); + logger.Exception("Exception while calling " + obj.GetType().Name + "." + method.Name + "() :\n", e); } } } @@ -477,7 +460,7 @@ private IEnumerator ProcessPatch(bool blocking) private void LoadPhysicsConfig() { - log("Loading Physics.cfg"); + logger.Info("Loading Physics.cfg"); UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); // need to use a file with a cfg extenssion to get the right fileType or you can't AddConfig on it physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); @@ -497,13 +480,13 @@ private void SaveModdedPhysics() if (configs.Count == 0) { - log("No PHYSICSGLOBALS node found. No custom Physics config will be saved"); + logger.Info("No PHYSICSGLOBALS node found. No custom Physics config will be saved"); return; } if (configs.Count > 1) { - log(configs.Count + " PHYSICSGLOBALS node found. A patch may be wrong. Using the first one"); + logger.Info(configs.Count + " PHYSICSGLOBALS node found. A patch may be wrong. Using the first one"); } configs[0].config.Save(physicsPath); @@ -535,7 +518,7 @@ private string FileSHA(string filename) } catch (Exception e) { - log("Exception hashing file " + filename + "\n" + e.ToString()); + logger.Exception("Exception hashing file " + filename, e); return "0"; } return "0"; @@ -570,7 +553,7 @@ private void IsCacheUpToDate() } else { - log("Duplicate fileSha key. This should not append. The key is " + files[i].url); + logger.Warning("Duplicate fileSha key. This should not append. The key is " + files[i].url); } } @@ -588,8 +571,8 @@ private void IsCacheUpToDate() sw.Stop(); - log("SHA generated in " + ((float)sw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); - log(" SHA = " + configSha); + logger.Info("SHA generated in " + ((float)sw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); + logger.Info(" SHA = " + configSha); useCache = false; if (File.Exists(shaPath)) @@ -608,8 +591,8 @@ private void IsCacheUpToDate() useCache = useCache && File.Exists(cachePath); useCache = useCache && File.Exists(physicsPath); useCache = useCache && File.Exists(techTreePath); - log("Cache SHA = " + storedSHA); - log("useCache = " + useCache); + logger.Info("Cache SHA = " + storedSHA); + logger.Info("useCache = " + useCache); } } } @@ -650,7 +633,7 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) noChange = false; } if (!noChange) - log("Changes :\n" + changes.ToString()); + logger.Info("Changes :\n" + changes.ToString()); return noChange; } @@ -676,7 +659,7 @@ private void CreateCache() ConfigNode cache = new ConfigNode(); - cache.AddValue("patchedNodeCount", patchedNodeCount.ToString()); + cache.AddValue("patchedNodeCount", progress.PatchedNodeCount.ToString()); foreach (UrlDir.UrlConfig config in GameDatabase.Instance.root.AllConfigs) { @@ -700,7 +683,7 @@ private void CreateCache() } } - log("Saving cache"); + logger.Info("Saving cache"); try { @@ -708,7 +691,7 @@ private void CreateCache() } catch (Exception e) { - log("Exception while saving the sha\n" + e.ToString()); + logger.Exception("Exception while saving the sha", e); } try { @@ -717,16 +700,16 @@ private void CreateCache() } catch (NullReferenceException e) { - log("NullReferenceException while saving the cache\n" + e.ToString()); + logger.Exception("NullReferenceException while saving the cache", e); } catch (Exception e) { - log("Exception while saving the cache\n" + e.ToString()); + logger.Exception("Exception while saving the cache", e); } try { - log("An error occured while creating the cache. Deleting the cache files to avoid keeping a bad cache"); + logger.Error("An error occured while creating the cache. Deleting the cache files to avoid keeping a bad cache"); if (File.Exists(cachePath)) File.Delete(cachePath); if (File.Exists(shaPath)) @@ -734,7 +717,7 @@ private void CreateCache() } catch (Exception e) { - log("Exception while deleting the cache\n" + e.ToString()); + logger.Exception("Exception while deleting the cache", e); } } @@ -744,13 +727,13 @@ private void SaveModdedTechTree() if (configs.Length == 0) { - log("No TechTree node found. No custom TechTree will be saved"); + logger.Info("No TechTree node found. No custom TechTree will be saved"); return; } if (configs.Length > 1) { - log(configs.Length + " TechTree node found. A patch may be wrong. Using the first one"); + logger.Info(configs.Length + " TechTree node found. A patch may be wrong. Using the first one"); } ConfigNode techNode = new ConfigNode("TechTree"); @@ -769,8 +752,10 @@ private void LoadCache() // And then load all the cached configs ConfigNode cache = ConfigNode.Load(cachePath); - if (cache.HasValue("patchedNodeCount")) - int.TryParse(cache.GetValue("patchedNodeCount"), out patchedNodeCount); + int patchedNodeCount; + + if (cache.HasValue("patchedNodeCount") && int.TryParse(cache.GetValue("patchedNodeCount"), out patchedNodeCount)) + progress.PatchedNodeCount = patchedNodeCount; // Create the fake file where we load the physic config cache UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); @@ -792,21 +777,21 @@ private void LoadCache() } else { - log("Parent null for " + parentUrl); + logger.Warning("Parent null for " + parentUrl); } } - log("Cache Loaded"); + logger.Info("Cache Loaded"); } private void StatusUpdate() { - status = "ModuleManager: " + patchedNodeCount + " patch" + (patchedNodeCount != 1 ? "es" : "") + (useCache ? " loaded from cache" : " applied"); + status = "ModuleManager: " + progress.PatchedNodeCount + " patch" + (progress.PatchedNodeCount != 1 ? "es" : "") + (useCache ? " loaded from cache" : " applied"); - if (errorCount > 0) - status += ", found " + errorCount + " error" + (errorCount != 1 ? "s" : "") + ""; + if (progress.ErrorCount > 0) + status += ", found " + progress.ErrorCount + " error" + (progress.ErrorCount != 1 ? "s" : "") + ""; - if (exceptionCount > 0) - status += ", encountered " + exceptionCount + " exception" + (exceptionCount != 1 ? "s" : "") + ""; + if (progress.ExceptionCount > 0) + status += ", encountered " + progress.ExceptionCount + " exception" + (progress.ExceptionCount != 1 ? "s" : "") + ""; } #region Needs checking @@ -823,7 +808,7 @@ private void CheckNeeds() { if (mod.config.name == null) { - log("Error - Node in file " + currentMod.parent.url + " subnode: " + currentMod.type + + progress.Error(currentMod, "Error - Node in file " + currentMod.parent.url + " subnode: " + currentMod.type + " has config.name == null"); } @@ -834,9 +819,7 @@ private void CheckNeeds() if (!CheckNeeds(ref type)) { - log("Deleting Node in file " + currentMod.parent.url + " subnode: " + currentMod.type + - " as it can't satisfy its NEEDS"); - needsUnsatisfiedCount++; + progress.NeedsUnsatisfiedNode(currentMod.parent.url, currentMod.type); continue; } @@ -847,13 +830,12 @@ private void CheckNeeds() } // Recursively check the contents - CheckNeeds(new NodeStack(mod.config), new PatchContext(mod, GameDatabase.Instance.root)); + CheckNeeds(new NodeStack(mod.config), new PatchContext(mod, GameDatabase.Instance.root, logger, progress)); } catch (Exception ex) { - log("Exception while checking needs : " + currentMod.url + " with a type of " + currentMod.type + "\n" + ex); - log("Node is : " + PrettyConfig(currentMod)); - exceptionCount++; + progress.Exception(currentMod, "Exception while checking needs : " + currentMod.url + " with a type of " + currentMod.type, ex); + logger.Error("Node is : " + PrettyConfig(currentMod)); } } } @@ -876,20 +858,17 @@ private void CheckNeeds(NodeStack stack, PatchContext context) else { needsCopy = true; - log( - "Deleting value in file: " + context.patchUrl.url + " subnode: " + stack.GetPath() + - " value: " + val.name + " = " + val.value + " as it can't satisfy its NEEDS"); - needsUnsatisfiedCount++; + context.progress.NeedsUnsatisfiedValue(context.patchUrl.url, stack.GetPath(), val.name); } } catch (ArgumentOutOfRangeException e) { - log("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"\n" + e); + progress.Exception("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"", e); throw; } catch (Exception e) { - log("General Exception " + e.GetType().Name + " for value \"" + val.name + " = " + val.value + "\"\n" + e.ToString()); + progress.Exception("General Exception in CheckNeeds for value \"" + val.name + "\"", e); throw; } } @@ -901,7 +880,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context) if (nodeName == null) { - log("Error - Node in file " + context.patchUrl.url + " subnode: " + stack.GetPath() + + progress.Error(context.patchUrl, "Error - Node in file " + context.patchUrl.url + " subnode: " + stack.GetPath() + " has config.name == null"); } @@ -916,20 +895,17 @@ private void CheckNeeds(NodeStack stack, PatchContext context) else { needsCopy = true; - log( - "Deleting node in file: " + context.patchUrl.url + " subnode: " + stack.GetPath() + "/" + - node.name + " as it can't satisfy its NEEDS"); - needsUnsatisfiedCount++; + progress.NeedsUnsatisfiedNode(context.patchUrl.url, stack.Push(node).GetPath()); } } catch (ArgumentOutOfRangeException e) { - log("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"\n" + e); + progress.Exception("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"", e); throw; } catch (Exception e) { - log("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"\n " + e.ToString()); + progress.Exception("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"", e); throw; } } @@ -999,7 +975,7 @@ private void PurgeUnused() public IEnumerator ApplyPatch(string Stage) { StatusUpdate(); - log(Stage + (Stage == ":LEGACY" ? " (default) pass" : " pass")); + logger.Info(Stage + (Stage == ":LEGACY" ? " (default) pass" : " pass")); yield return null; activity = "ModuleManager " + Stage; @@ -1011,8 +987,6 @@ public IEnumerator ApplyPatch(string Stage) for (int modsIndex = 0; modsIndex < allConfigs.Length; modsIndex++) { UrlDir.UrlConfig mod = allConfigs[modsIndex]; - int lastErrorCount = errorCount; - int lastExceptionCount = exceptionCount; try { string name = RemoveWS(mod.type); @@ -1023,10 +997,9 @@ public IEnumerator ApplyPatch(string Stage) { if (!IsBracketBalanced(mod.type)) { - log( + progress.Error(mod, "Error - Skipping a patch with unbalanced square brackets or a space (replace them with a '?') :\n" + mod.name + "\n"); - errorCount++; // And remove it so it's not tried anymore mod.parent.configs.Remove(mod); @@ -1049,7 +1022,7 @@ public IEnumerator ApplyPatch(string Stage) try { - PatchContext context = new PatchContext(mod, GameDatabase.Instance.root); + PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); char[] sep = { '[', ']' }; string condition = ""; @@ -1077,8 +1050,7 @@ public IEnumerator ApplyPatch(string Stage) switch (cmd) { case Command.Edit: - log("Applying node " + mod.url + " to " + url.url); - patchedNodeCount++; + progress.NodePatched(url.url, mod.url); url.config = ModifyNode(new NodeStack(url.config), mod.config, context); break; @@ -1086,19 +1058,18 @@ public IEnumerator ApplyPatch(string Stage) ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); if (url.config.name != mod.name) { - log("Copying Node " + url.config.name + " into " + clone.name); + progress.NodeCopied(url.url, mod.url); url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); } else { - errorCount++; - log("Error - Error while processing " + mod.config.name + + progress.Error(mod, "Error - Error while processing " + mod.config.name + " the copy needs to have a different name than the parent (use @name = xxx)"); } break; case Command.Delete: - log("Deleting Node " + url.config.name); + progress.NodeDeleted(url.url, mod.url); url.parent.configs.Remove(url); break; @@ -1115,7 +1086,7 @@ public IEnumerator ApplyPatch(string Stage) // When this special node is found then try to apply the patch once more on the same NODE if (mod.config.HasNode("MM_PATCH_LOOP")) { - log("Looping on " + mod.url + " to " + url.url); + logger.Info("Looping on " + mod.url + " to " + url.url); loop = true; } } @@ -1131,23 +1102,16 @@ public IEnumerator ApplyPatch(string Stage) finally { // The patch was either run or has failed, in any case let's remove it from the database - appliedPatchCount++; mod.parent.configs.Remove(mod); } } } catch (Exception e) { - log("Exception while processing node : " + mod.url + "\n" + e); - exceptionCount++; - log("Processed node was\n" + PrettyConfig(mod)); + progress.Exception(mod, "Exception while processing node : " + mod.url, e); + logger.Error("Processed node was\n" + PrettyConfig(mod)); mod.parent.configs.Remove(mod); } - finally - { - if (lastErrorCount < errorCount || lastExceptionCount < exceptionCount) - addErrorFiles(mod.parent, errorCount - lastErrorCount + exceptionCount - lastExceptionCount); - } if (nextYield < Time.realtimeSinceStartup) { nextYield = Time.realtimeSinceStartup + yieldInterval; @@ -1167,7 +1131,7 @@ public IEnumerator ApplyPatch(string Stage) // ModifyNode applies the ConfigNode mod as a 'patch' to ConfigNode original, then returns the patched ConfigNode. // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse. - public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext context) + public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext context) { ConfigNode newNode = DeepCopy(original.value); NodeStack nodeStack = original.ReplaceValue(newNode); @@ -1190,8 +1154,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co Match assignMatch = parseAssign.Match(valName); if (!assignMatch.Success) { - log("Error - Cannot parse value assigning command: " + valName); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Cannot parse value assigning command: " + valName); continue; } @@ -1201,8 +1164,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co if (val == null) { - log("Error - Cannot find value assigning command: " + valName); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Cannot find value assigning command: " + valName); continue; } @@ -1245,8 +1207,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co Match match = parseValue.Match(valName); if (!match.Success) { - log("Error - Cannot parse value modifying command: " + valName); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Cannot parse value modifying command: " + valName); continue; } @@ -1262,8 +1223,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co isPosStar = true; else if (!int.TryParse(match.Groups[3].Value, out position)) { - Debug.LogError("Error - Unable to parse number as number. Very odd."); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Unable to parse number as number. Very odd."); continue; } } @@ -1283,8 +1243,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co // can have "node,n *" (for *= ect) else if (!int.TryParse(match.Groups[2].Value, out index)) { - Debug.LogError("Error - Unable to parse number as number. Very odd."); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Unable to parse number as number. Very odd."); continue; } } @@ -1304,8 +1263,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co case Command.Insert: if (match.Groups[5].Success) { - log("Error - Cannot use operators with insert value: " + mod.name); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Cannot use operators with insert value: " + mod.name); } else { @@ -1314,11 +1272,8 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co if (varValue != null) InsertValue(newNode, match.Groups[2].Success ? index : int.MaxValue, valName, varValue); else - { - log("Error - Cannot parse variable search when inserting new key " + valName + " = " + + context.progress.Error(context.patchUrl, "Error - Cannot parse variable search when inserting new key " + valName + " = " + modVal.value); - errorCount++; - } } break; @@ -1327,12 +1282,11 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co || valName.Contains('?')) { if (match.Groups[2].Success) - log("Error - Cannot use index with replace (%) value: " + mod.name); + context.progress.Error(context.patchUrl, "Error - Cannot use index with replace (%) value: " + mod.name); if (match.Groups[5].Success) - log("Error - Cannot use operators with replace (%) value: " + mod.name); + context.progress.Error(context.patchUrl, "Error - Cannot use operators with replace (%) value: " + mod.name); if (valName.Contains('*') || valName.Contains('?')) - log("Error - Cannot use wildcards (* or ?) with replace (%) value: " + mod.name); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Cannot use wildcards (* or ?) with replace (%) value: " + mod.name); } else { @@ -1344,9 +1298,8 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co } else { - log("Error - Cannot parse variable search when replacing (%) key " + valName + " = " + + context.progress.Error(context.patchUrl, "Error - Cannot parse variable search when replacing (%) key " + valName + " = " + modVal.value); - errorCount++; } } break; @@ -1365,7 +1318,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co { ConfigNode.Value origVal; string value = FindAndReplaceValue(mod, ref valName, varValue, newNode, op, index, - out origVal, match.Groups[3].Success, position, isPosStar, seperator); + out origVal, context, match.Groups[3].Success, position, isPosStar, seperator); if (value != null) { @@ -1382,8 +1335,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co } else { - log("Error - Cannot parse variable search when editing key " + valName + " = " + modVal.value); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Cannot parse variable search when editing key " + valName + " = " + modVal.value); } if (isStar) index++; @@ -1394,8 +1346,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co case Command.Delete: if (match.Groups[5].Success) { - log("Error - Cannot use operators with delete (- or !) value: " + mod.name); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Cannot use operators with delete (- or !) value: " + mod.name); } else if (match.Groups[2].Success) { @@ -1432,8 +1383,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co case Command.Rename: if (nodeStack.IsRoot) { - log("Error - Renaming nodes does not work on top nodes"); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Renaming nodes does not work on top nodes"); break; } newNode.name = modVal.value; @@ -1444,12 +1394,11 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co || valName.Contains('?')) { if (match.Groups[2].Success) - log("Error - Cannot use index with create (&) value: " + mod.name); + context.progress.Error(context.patchUrl, "Error - Cannot use index with create (&) value: " + mod.name); if (match.Groups[5].Success) - log("Error - Cannot use operators with create (&) value: " + mod.name); + context.progress.Error(context.patchUrl, "Error - Cannot use operators with create (&) value: " + mod.name); if (valName.Contains('*') || valName.Contains('?')) - log("Error - Cannot use wildcards (* or ?) with create (&) value: " + mod.name); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Cannot use wildcards (* or ?) with create (&) value: " + mod.name); } else { @@ -1461,9 +1410,8 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co } else { - log("Error - Cannot parse variable search when replacing (&) key " + valName + " = " + + context.progress.Error(context.patchUrl, "Error - Cannot parse variable search when replacing (&) key " + valName + " = " + modVal.value); - errorCount++; } } break; @@ -1483,10 +1431,9 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co if (!IsBracketBalanced(subMod.name)) { - log( + context.progress.Error(context.patchUrl, "Error - Skipping a patch subnode with unbalanced square brackets or a space (replace them with a '?') in " + mod.name + " : \n" + subMod.name + "\n"); - errorCount++; continue; } @@ -1529,8 +1476,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co if (toPaste == null) { - log("Error - Can not find the node to paste in " + mod.name + " : " + subMod.name + "\n"); - errorCount++; + context.progress.Error(context.patchUrl, "Error - Can not find the node to paste in " + mod.name + " : " + subMod.name + "\n"); continue; } @@ -1714,7 +1660,7 @@ public ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext co // Search for a ConfigNode by a path alike string - private ConfigNode RecurseNodeSearch(string path, NodeStack nodeStack, PatchContext context) + private static ConfigNode RecurseNodeSearch(string path, NodeStack nodeStack, PatchContext context) { //log("Path : \"" + path + "\""); @@ -1775,7 +1721,7 @@ private ConfigNode RecurseNodeSearch(string path, NodeStack nodeStack, PatchCont IEnumerable urlConfigs = context.databaseRoot.GetConfigs(nodeType); if (!urlConfigs.Any()) { - log("Can't find nodeType:" + nodeType); + context.logger.Warning("Can't find nodeType:" + nodeType); return null; } @@ -1876,7 +1822,7 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod IEnumerable urlConfigs = context.databaseRoot.GetConfigs(nodeType); if (!urlConfigs.Any()) { - log("Can't find nodeType:" + nodeType); + context.logger.Warning("Can't find nodeType:" + nodeType); return null; } @@ -1972,7 +1918,7 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod Match match = parseVarKey.Match(path); if (!match.Success) { - log("Cannot parse variable search command: " + path); + context.logger.Warning("Cannot parse variable search command: " + path); return null; } @@ -1985,7 +1931,7 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod ConfigNode.Value cVal = FindValueIn(nodeStack.value, valName, idx); if (cVal == null) { - log("Cannot find key " + valName + " in " + nodeStack.value.name); + context.logger.Warning("Cannot find key " + valName + " in " + nodeStack.value.name); return null; } @@ -2037,7 +1983,7 @@ private static string ProcessVariableSearch(string value, NodeStack nodeStack, P return value; } - private string FindAndReplaceValue( + private static string FindAndReplaceValue( ConfigNode mod, ref string valName, string value, @@ -2045,6 +1991,7 @@ private string FindAndReplaceValue( char op, int index, out ConfigNode.Value origVal, + PatchContext context, bool hasPosIndex = false, int posIndex = 0, bool hasPosStar = false, @@ -2061,8 +2008,7 @@ private string FindAndReplaceValue( strArray = oValue.Split(new char[] { seperator }, StringSplitOptions.RemoveEmptyEntries); if (posIndex >= strArray.Length) { - log("Invalid Vector Index!"); - errorCount++; + context.progress.Error(context.patchUrl, "Invalid Vector Index!"); return null; } } @@ -2093,11 +2039,9 @@ private string FindAndReplaceValue( } catch (Exception ex) { - log("Error - Failed to do a regexp replacement: " + mod.name + " : original value=\"" + oValue + + context.progress.Exception(context.patchUrl, "Error - Failed to do a regexp replacement: " + mod.name + " : original value=\"" + oValue + "\" regexp=\"" + value + - "\" \nNote - to use regexp, the first char is used to subdivide the string (much like sed)\n" + - ex); - errorCount++; + "\" \nNote - to use regexp, the first char is used to subdivide the string (much like sed)", ex); return null; } } @@ -2128,9 +2072,8 @@ private string FindAndReplaceValue( } else { - log("Error - Failed to do a maths replacement: " + mod.name + " : original value=\"" + oValue + + context.progress.Error(context.patchUrl, "Error - Failed to do a maths replacement: " + mod.name + " : original value=\"" + oValue + "\" operator=" + op + " mod value=\"" + value + "\""); - errorCount++; return null; } } @@ -2499,7 +2442,7 @@ private string PrettyConfig(UrlDir.UrlConfig config) } catch (Exception e) { - log("PrettyConfig Exception " + e); + logger.Exception("PrettyConfig Exception", e); } return sb.ToString(); } @@ -2520,10 +2463,10 @@ private void PrettyConfig(ConfigNode node, ref StringBuilder sb, string indent) } catch (Exception) { - log("value.name.Length=" + value.name.Length); - log("value.name.IsNullOrEmpty=" + string.IsNullOrEmpty(value.name)); - log("n " + value.name); - log("v " + value.value); + logger.Error("value.name.Length=" + value.name.Length); + logger.Error("value.name.IsNullOrEmpty=" + string.IsNullOrEmpty(value.name)); + logger.Error("n " + value.name); + logger.Error("v " + value.value); throw; } } @@ -2640,31 +2583,11 @@ private static bool CompareRecursive(ConfigNode expectNode, ConfigNode gotNode) #endregion Config Node Utilities - #region logging - - public void addErrorFiles(UrlDir.UrlFile file, int n = 1) - { - string key = file.url + "." + file.fileExtension; - if (key[0] == '/') - key = key.Substring(1); - if (!errorFiles.ContainsKey(key)) - errorFiles.Add(key, n); - else - errorFiles[key] = errorFiles[key] + n; - } - - public static void log(String s) - { - print("[ModuleManager] " + s); - } - - #endregion logging - #region Tests private void RunTestCases() { - log("Running tests..."); + logger.Info("Running tests..."); // Do MM testcases foreach (UrlDir.UrlConfig expect in GameDatabase.Instance.GetConfigs("MMTEST_EXPECT")) @@ -2673,10 +2596,10 @@ private void RunTestCases() UrlDir.UrlFile parent = expect.parent; if (parent.configs.Count != expect.config.CountNodes + 1) { - log("Test " + parent.name + " failed as expected number of nodes differs expected:" + + logger.Error("Test " + parent.name + " failed as expected number of nodes differs expected:" + expect.config.CountNodes + " found: " + parent.configs.Count); for (int i = 0; i < parent.configs.Count; ++i) - log(parent.configs[i].config.ToString()); + logger.Info(parent.configs[i].config.ToString()); continue; } for (int i = 0; i < expect.config.CountNodes; ++i) @@ -2685,7 +2608,7 @@ private void RunTestCases() ConfigNode expectNode = expect.config.nodes[i]; if (!CompareRecursive(expectNode, gotNode)) { - log("Test " + parent.name + "[" + i + + logger.Error("Test " + parent.name + "[" + i + "] failed as expected output and actual output differ.\nexpected:\n" + expectNode + "\nActually got:\n" + gotNode); } @@ -2694,7 +2617,7 @@ private void RunTestCases() // Purge the tests parent.configs.Clear(); } - log("tests complete."); + logger.Info("tests complete."); } #endregion Tests diff --git a/ModuleManager.cs b/ModuleManager.cs index 6b3d1b1a..9bf3073f 100644 --- a/ModuleManager.cs +++ b/ModuleManager.cs @@ -297,9 +297,9 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) while (!MMPatchLoader.Instance.IsReady()) yield return null; - log("DB Reload OK with patchCount=" + MMPatchLoader.Instance.patchedNodeCount + " errorCount=" + - MMPatchLoader.Instance.errorCount + " needsUnsatisfiedCount=" + - MMPatchLoader.Instance.needsUnsatisfiedCount + " exceptionCount=" + MMPatchLoader.Instance.exceptionCount); + log("DB Reload OK with patchCount=" + MMPatchLoader.Instance.progress.PatchedNodeCount + " errorCount=" + + MMPatchLoader.Instance.progress.ErrorCount + " needsUnsatisfiedCount=" + + MMPatchLoader.Instance.progress.NeedsUnsatisfiedCount + " exceptionCount=" + MMPatchLoader.Instance.progress.ExceptionCount); PartResourceLibrary.Instance.LoadDefinitions(); diff --git a/PatchContext.cs b/PatchContext.cs index ba9ea83f..2e1bc3e0 100644 --- a/PatchContext.cs +++ b/PatchContext.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using ModuleManager.Logging; namespace ModuleManager { @@ -9,11 +7,15 @@ public struct PatchContext { public readonly UrlDir.UrlConfig patchUrl; public readonly UrlDir databaseRoot; + public readonly IBasicLogger logger; + public readonly IPatchProgress progress; - public PatchContext(UrlDir.UrlConfig patchUrl, UrlDir databaseRoot) + public PatchContext(UrlDir.UrlConfig patchUrl, UrlDir databaseRoot, IBasicLogger logger, IPatchProgress progress) { this.patchUrl = patchUrl; this.databaseRoot = databaseRoot; + this.logger = logger; + this.progress = progress; } } } From 55fc4e6cc9dddb4b3c3e634002d893d987923463 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 28 Aug 2017 23:39:21 -0700 Subject: [PATCH 047/342] Remove blocking option It's no longer used --- MMPatchLoader.cs | 47 ++++++++++++++--------------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index 91494d46..b0c4d2f2 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -112,17 +112,6 @@ public override string ProgressTitle() } public override void StartLoad() - { - StartLoad(false); - } - - public void Update() - { - if (progress.AppliedPatchCount > 0 && HighLogic.LoadedScene == GameScenes.LOADING) - StatusUpdate(); - } - - public void StartLoad(bool blocking) { patchSw.Reset(); patchSw.Start(); @@ -134,7 +123,13 @@ public void StartLoad(bool blocking) // DB check used to track the now fixed TextureReplacer corruption //checkValues(); - StartCoroutine(ProcessPatch(blocking), blocking); + StartCoroutine(ProcessPatch()); + } + + public void Update() + { + if (progress.AppliedPatchCount > 0 && HighLogic.LoadedScene == GameScenes.LOADING) + StatusUpdate(); } public static void addPostPatchCallback(ModuleManagerPostPatchCallback callback) @@ -236,21 +231,7 @@ private void PrePatchInit() #endregion List of mods } - - Coroutine StartCoroutine(IEnumerator enumerator, bool blocking) - { - if (blocking) - { - while (enumerator.MoveNext()) { } - return null; - } - else - { - return StartCoroutine(enumerator); - } - } - - private IEnumerator ProcessPatch(bool blocking) + private IEnumerator ProcessPatch() { status = "Checking Cache"; logger.Info(status); @@ -307,21 +288,21 @@ private IEnumerator ProcessPatch(bool blocking) yield return null; // :First node - yield return StartCoroutine(ApplyPatch(":FIRST"), blocking); + yield return StartCoroutine(ApplyPatch(":FIRST")); // any node without a :pass - yield return StartCoroutine(ApplyPatch(":LEGACY"), blocking); + yield return StartCoroutine(ApplyPatch(":LEGACY")); foreach (string mod in mods) { string upperModName = mod.ToUpper(); - yield return StartCoroutine(ApplyPatch(":BEFORE[" + upperModName + "]"), blocking); - yield return StartCoroutine(ApplyPatch(":FOR[" + upperModName + "]"), blocking); - yield return StartCoroutine(ApplyPatch(":AFTER[" + upperModName + "]"), blocking); + yield return StartCoroutine(ApplyPatch(":BEFORE[" + upperModName + "]")); + yield return StartCoroutine(ApplyPatch(":FOR[" + upperModName + "]")); + yield return StartCoroutine(ApplyPatch(":AFTER[" + upperModName + "]")); } // :Final node - yield return StartCoroutine(ApplyPatch(":FINAL"), blocking); + yield return StartCoroutine(ApplyPatch(":FINAL")); PurgeUnused(); From bf640f41e969b14922242a6a6fad577fa8f48b34 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 28 Aug 2017 23:42:50 -0700 Subject: [PATCH 048/342] Use inline variable declaration --- MMPatchLoader.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index b0c4d2f2..b8a38e61 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -732,10 +732,8 @@ private void LoadCache() // And then load all the cached configs ConfigNode cache = ConfigNode.Load(cachePath); - - int patchedNodeCount; - - if (cache.HasValue("patchedNodeCount") && int.TryParse(cache.GetValue("patchedNodeCount"), out patchedNodeCount)) + + if (cache.HasValue("patchedNodeCount") && int.TryParse(cache.GetValue("patchedNodeCount"), out int patchedNodeCount)) progress.PatchedNodeCount = patchedNodeCount; // Create the fake file where we load the physic config cache From f751ed43916ed68a37c5f3d921c8caa41c67ca0e Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 29 Aug 2017 23:08:37 -0700 Subject: [PATCH 049/342] Make log messages consistent --- PatchProgress.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PatchProgress.cs b/PatchProgress.cs index 64e02d03..63bb0fd3 100644 --- a/PatchProgress.cs +++ b/PatchProgress.cs @@ -44,18 +44,18 @@ public void PatchAdded() public void NodePatched(string url, string patchUrl) { - logger.Info($"Applying node {patchUrl} to {url}"); + logger.Info($"Applying update {patchUrl} to {url}"); PatchedNodeCount += 1; } public void NodeCopied(string url, string patchUrl) { - logger.Info($"Copying Node {url} using {patchUrl}"); + logger.Info($"Applying copy {patchUrl} to {url}"); } public void NodeDeleted(string url, string patchUrl) { - logger.Info($"Deleting Node {url} using {patchUrl}"); + logger.Info($"Applying delete {patchUrl} to {url}"); } public void PatchApplied() From f03b5789becb4fb64dfd237ee12490bd2f1fa654 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 29 Aug 2017 23:17:15 -0700 Subject: [PATCH 050/342] Make deletes and copies count toward patch count --- PatchProgress.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PatchProgress.cs b/PatchProgress.cs index 63bb0fd3..19326308 100644 --- a/PatchProgress.cs +++ b/PatchProgress.cs @@ -51,11 +51,13 @@ public void NodePatched(string url, string patchUrl) public void NodeCopied(string url, string patchUrl) { logger.Info($"Applying copy {patchUrl} to {url}"); + PatchedNodeCount += 1; } public void NodeDeleted(string url, string patchUrl) { logger.Info($"Applying delete {patchUrl} to {url}"); + PatchedNodeCount += 1; } public void PatchApplied() From 51b6f75a9e2462d28fc67967cfab16cfcb92b24d Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 29 Aug 2017 23:18:50 -0700 Subject: [PATCH 051/342] Make names more accurate These are called before the patch is applied --- IPatchProgress.cs | 6 +++--- MMPatchLoader.cs | 6 +++--- PatchProgress.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/IPatchProgress.cs b/IPatchProgress.cs index e3c0a067..43895e32 100644 --- a/IPatchProgress.cs +++ b/IPatchProgress.cs @@ -19,9 +19,9 @@ public interface IPatchProgress void Exception(UrlDir.UrlConfig url, string message, Exception exception); void NeedsUnsatisfiedNode(string url, string path); void NeedsUnsatisfiedValue(string url, string path, string valName); - void NodeCopied(string url, string patchUrl); - void NodeDeleted(string url, string patchUrl); - void NodePatched(string url, string patchUrl); + void ApplyingCopy(string url, string patchUrl); + void ApplyingDelete(string url, string patchUrl); + void ApplyingUpdate(string url, string patchUrl); void PatchAdded(); void PatchApplied(); } diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index b8a38e61..44b767f8 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -1029,7 +1029,7 @@ public IEnumerator ApplyPatch(string Stage) switch (cmd) { case Command.Edit: - progress.NodePatched(url.url, mod.url); + progress.ApplyingUpdate(url.url, mod.url); url.config = ModifyNode(new NodeStack(url.config), mod.config, context); break; @@ -1037,7 +1037,7 @@ public IEnumerator ApplyPatch(string Stage) ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); if (url.config.name != mod.name) { - progress.NodeCopied(url.url, mod.url); + progress.ApplyingCopy(url.url, mod.url); url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); } else @@ -1048,7 +1048,7 @@ public IEnumerator ApplyPatch(string Stage) break; case Command.Delete: - progress.NodeDeleted(url.url, mod.url); + progress.ApplyingDelete(url.url, mod.url); url.parent.configs.Remove(url); break; diff --git a/PatchProgress.cs b/PatchProgress.cs index 19326308..e4d3f6a2 100644 --- a/PatchProgress.cs +++ b/PatchProgress.cs @@ -42,19 +42,19 @@ public void PatchAdded() TotalPatchCount += 1; } - public void NodePatched(string url, string patchUrl) + public void ApplyingUpdate(string url, string patchUrl) { logger.Info($"Applying update {patchUrl} to {url}"); PatchedNodeCount += 1; } - public void NodeCopied(string url, string patchUrl) + public void ApplyingCopy(string url, string patchUrl) { logger.Info($"Applying copy {patchUrl} to {url}"); PatchedNodeCount += 1; } - public void NodeDeleted(string url, string patchUrl) + public void ApplyingDelete(string url, string patchUrl) { logger.Info($"Applying delete {patchUrl} to {url}"); PatchedNodeCount += 1; From 8179c4405eca0916cd2edc3c9c4b8572cd6fd7c2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 29 Aug 2017 23:23:15 -0700 Subject: [PATCH 052/342] Simplify null check --- MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMPatchLoader.cs b/MMPatchLoader.cs index 44b767f8..5e503a08 100644 --- a/MMPatchLoader.cs +++ b/MMPatchLoader.cs @@ -586,7 +586,7 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) for (int i = 0; i < files.Length; i++) { ConfigNode fileNode = getFileNode(shaConfigNode, files[i].url); - string fileSha = fileNode != null ? fileNode.GetValue("SHA") : null; + string fileSha = fileNode?.GetValue("SHA"); if (fileNode == null) continue; From 1a12a253a3addda038bfcd8fef7fa0b0f1840c0c Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 21:26:59 -0700 Subject: [PATCH 053/342] move main project to its own directory Allows more to be added --- ModuleManager.sln | 2 +- {Cats => ModuleManager/Cats}/CatAnimator.cs | 0 {Cats => ModuleManager/Cats}/CatManager.cs | 0 {Cats => ModuleManager/Cats}/CatMover.cs | 0 {Cats => ModuleManager/Cats}/CatOrbiter.cs | 0 .../Collections}/ImmutableStack.cs | 0 .../CustomConfigsManager.cs | 0 .../Extensions}/NodeStackExtensions.cs | 0 .../IPatchProgress.cs | 0 {Logging => ModuleManager/Logging}/IBasicLogger.cs | 0 {Logging => ModuleManager/Logging}/ModLogger.cs | 0 MMPatchLoader.cs => ModuleManager/MMPatchLoader.cs | 0 ModuleManager.cs => ModuleManager/ModuleManager.cs | 0 .../ModuleManager.csproj | 0 PatchContext.cs => ModuleManager/PatchContext.cs | 0 PatchProgress.cs => ModuleManager/PatchProgress.cs | 0 .../Properties}/AssemblyInfo.cs | 0 .../Properties}/Resources.Designer.cs | 0 .../Properties}/Resources.resx | 0 {Properties => ModuleManager/Properties}/cat-1.png | Bin {Properties => ModuleManager/Properties}/cat-10.png | Bin {Properties => ModuleManager/Properties}/cat-11.png | Bin {Properties => ModuleManager/Properties}/cat-12.png | Bin {Properties => ModuleManager/Properties}/cat-2.png | Bin {Properties => ModuleManager/Properties}/cat-3.png | Bin {Properties => ModuleManager/Properties}/cat-4.png | Bin {Properties => ModuleManager/Properties}/cat-5.png | Bin {Properties => ModuleManager/Properties}/cat-6.png | Bin {Properties => ModuleManager/Properties}/cat-7.png | Bin {Properties => ModuleManager/Properties}/cat-8.png | Bin {Properties => ModuleManager/Properties}/cat-9.png | Bin .../Properties}/rainbow2.png | Bin packages.config => ModuleManager/packages.config | 0 33 files changed, 1 insertion(+), 1 deletion(-) rename {Cats => ModuleManager/Cats}/CatAnimator.cs (100%) rename {Cats => ModuleManager/Cats}/CatManager.cs (100%) rename {Cats => ModuleManager/Cats}/CatMover.cs (100%) rename {Cats => ModuleManager/Cats}/CatOrbiter.cs (100%) rename {Collections => ModuleManager/Collections}/ImmutableStack.cs (100%) rename CustomConfigsManager.cs => ModuleManager/CustomConfigsManager.cs (100%) rename {Extensions => ModuleManager/Extensions}/NodeStackExtensions.cs (100%) rename IPatchProgress.cs => ModuleManager/IPatchProgress.cs (100%) rename {Logging => ModuleManager/Logging}/IBasicLogger.cs (100%) rename {Logging => ModuleManager/Logging}/ModLogger.cs (100%) rename MMPatchLoader.cs => ModuleManager/MMPatchLoader.cs (100%) rename ModuleManager.cs => ModuleManager/ModuleManager.cs (100%) rename ModuleManager.csproj => ModuleManager/ModuleManager.csproj (100%) rename PatchContext.cs => ModuleManager/PatchContext.cs (100%) rename PatchProgress.cs => ModuleManager/PatchProgress.cs (100%) rename {Properties => ModuleManager/Properties}/AssemblyInfo.cs (100%) rename {Properties => ModuleManager/Properties}/Resources.Designer.cs (100%) rename {Properties => ModuleManager/Properties}/Resources.resx (100%) rename {Properties => ModuleManager/Properties}/cat-1.png (100%) rename {Properties => ModuleManager/Properties}/cat-10.png (100%) rename {Properties => ModuleManager/Properties}/cat-11.png (100%) rename {Properties => ModuleManager/Properties}/cat-12.png (100%) rename {Properties => ModuleManager/Properties}/cat-2.png (100%) rename {Properties => ModuleManager/Properties}/cat-3.png (100%) rename {Properties => ModuleManager/Properties}/cat-4.png (100%) rename {Properties => ModuleManager/Properties}/cat-5.png (100%) rename {Properties => ModuleManager/Properties}/cat-6.png (100%) rename {Properties => ModuleManager/Properties}/cat-7.png (100%) rename {Properties => ModuleManager/Properties}/cat-8.png (100%) rename {Properties => ModuleManager/Properties}/cat-9.png (100%) rename {Properties => ModuleManager/Properties}/rainbow2.png (100%) rename packages.config => ModuleManager/packages.config (100%) diff --git a/ModuleManager.sln b/ModuleManager.sln index ce332b6d..8ffcf7cf 100644 --- a/ModuleManager.sln +++ b/ModuleManager.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleManager", "ModuleManager.csproj", "{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleManager", "ModuleManager\ModuleManager.csproj", "{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Cats/CatAnimator.cs b/ModuleManager/Cats/CatAnimator.cs similarity index 100% rename from Cats/CatAnimator.cs rename to ModuleManager/Cats/CatAnimator.cs diff --git a/Cats/CatManager.cs b/ModuleManager/Cats/CatManager.cs similarity index 100% rename from Cats/CatManager.cs rename to ModuleManager/Cats/CatManager.cs diff --git a/Cats/CatMover.cs b/ModuleManager/Cats/CatMover.cs similarity index 100% rename from Cats/CatMover.cs rename to ModuleManager/Cats/CatMover.cs diff --git a/Cats/CatOrbiter.cs b/ModuleManager/Cats/CatOrbiter.cs similarity index 100% rename from Cats/CatOrbiter.cs rename to ModuleManager/Cats/CatOrbiter.cs diff --git a/Collections/ImmutableStack.cs b/ModuleManager/Collections/ImmutableStack.cs similarity index 100% rename from Collections/ImmutableStack.cs rename to ModuleManager/Collections/ImmutableStack.cs diff --git a/CustomConfigsManager.cs b/ModuleManager/CustomConfigsManager.cs similarity index 100% rename from CustomConfigsManager.cs rename to ModuleManager/CustomConfigsManager.cs diff --git a/Extensions/NodeStackExtensions.cs b/ModuleManager/Extensions/NodeStackExtensions.cs similarity index 100% rename from Extensions/NodeStackExtensions.cs rename to ModuleManager/Extensions/NodeStackExtensions.cs diff --git a/IPatchProgress.cs b/ModuleManager/IPatchProgress.cs similarity index 100% rename from IPatchProgress.cs rename to ModuleManager/IPatchProgress.cs diff --git a/Logging/IBasicLogger.cs b/ModuleManager/Logging/IBasicLogger.cs similarity index 100% rename from Logging/IBasicLogger.cs rename to ModuleManager/Logging/IBasicLogger.cs diff --git a/Logging/ModLogger.cs b/ModuleManager/Logging/ModLogger.cs similarity index 100% rename from Logging/ModLogger.cs rename to ModuleManager/Logging/ModLogger.cs diff --git a/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs similarity index 100% rename from MMPatchLoader.cs rename to ModuleManager/MMPatchLoader.cs diff --git a/ModuleManager.cs b/ModuleManager/ModuleManager.cs similarity index 100% rename from ModuleManager.cs rename to ModuleManager/ModuleManager.cs diff --git a/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj similarity index 100% rename from ModuleManager.csproj rename to ModuleManager/ModuleManager.csproj diff --git a/PatchContext.cs b/ModuleManager/PatchContext.cs similarity index 100% rename from PatchContext.cs rename to ModuleManager/PatchContext.cs diff --git a/PatchProgress.cs b/ModuleManager/PatchProgress.cs similarity index 100% rename from PatchProgress.cs rename to ModuleManager/PatchProgress.cs diff --git a/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs similarity index 100% rename from Properties/AssemblyInfo.cs rename to ModuleManager/Properties/AssemblyInfo.cs diff --git a/Properties/Resources.Designer.cs b/ModuleManager/Properties/Resources.Designer.cs similarity index 100% rename from Properties/Resources.Designer.cs rename to ModuleManager/Properties/Resources.Designer.cs diff --git a/Properties/Resources.resx b/ModuleManager/Properties/Resources.resx similarity index 100% rename from Properties/Resources.resx rename to ModuleManager/Properties/Resources.resx diff --git a/Properties/cat-1.png b/ModuleManager/Properties/cat-1.png similarity index 100% rename from Properties/cat-1.png rename to ModuleManager/Properties/cat-1.png diff --git a/Properties/cat-10.png b/ModuleManager/Properties/cat-10.png similarity index 100% rename from Properties/cat-10.png rename to ModuleManager/Properties/cat-10.png diff --git a/Properties/cat-11.png b/ModuleManager/Properties/cat-11.png similarity index 100% rename from Properties/cat-11.png rename to ModuleManager/Properties/cat-11.png diff --git a/Properties/cat-12.png b/ModuleManager/Properties/cat-12.png similarity index 100% rename from Properties/cat-12.png rename to ModuleManager/Properties/cat-12.png diff --git a/Properties/cat-2.png b/ModuleManager/Properties/cat-2.png similarity index 100% rename from Properties/cat-2.png rename to ModuleManager/Properties/cat-2.png diff --git a/Properties/cat-3.png b/ModuleManager/Properties/cat-3.png similarity index 100% rename from Properties/cat-3.png rename to ModuleManager/Properties/cat-3.png diff --git a/Properties/cat-4.png b/ModuleManager/Properties/cat-4.png similarity index 100% rename from Properties/cat-4.png rename to ModuleManager/Properties/cat-4.png diff --git a/Properties/cat-5.png b/ModuleManager/Properties/cat-5.png similarity index 100% rename from Properties/cat-5.png rename to ModuleManager/Properties/cat-5.png diff --git a/Properties/cat-6.png b/ModuleManager/Properties/cat-6.png similarity index 100% rename from Properties/cat-6.png rename to ModuleManager/Properties/cat-6.png diff --git a/Properties/cat-7.png b/ModuleManager/Properties/cat-7.png similarity index 100% rename from Properties/cat-7.png rename to ModuleManager/Properties/cat-7.png diff --git a/Properties/cat-8.png b/ModuleManager/Properties/cat-8.png similarity index 100% rename from Properties/cat-8.png rename to ModuleManager/Properties/cat-8.png diff --git a/Properties/cat-9.png b/ModuleManager/Properties/cat-9.png similarity index 100% rename from Properties/cat-9.png rename to ModuleManager/Properties/cat-9.png diff --git a/Properties/rainbow2.png b/ModuleManager/Properties/rainbow2.png similarity index 100% rename from Properties/rainbow2.png rename to ModuleManager/Properties/rainbow2.png diff --git a/packages.config b/ModuleManager/packages.config similarity index 100% rename from packages.config rename to ModuleManager/packages.config From f02f418db54ecb0ef523e08063e3d6c59922f5fd Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 21:29:51 -0700 Subject: [PATCH 054/342] Better output dir for debug --- ModuleManager/ModuleManager.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 450001fa..49779504 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -15,7 +15,7 @@ True full False - .. + bin\Debug\ DEBUG; prompt 4 From 95362b529ab739ad6c62d9a0a50ca7eaf60fe7f1 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 21:30:00 -0700 Subject: [PATCH 055/342] Do not copy local --- ModuleManager/ModuleManager.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 49779504..4e475df1 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -53,7 +53,9 @@ False False - + + False + False @@ -62,6 +64,7 @@ False C:\Games\KSPSteamController\KSPSteamCtrlr\KSPUnity-Steam-Symlinks\UnityEngine.UI.dll + False From c4ba1b943c1d94fdab9ac7e51c1ca5dc9f26e918 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 21:42:21 -0700 Subject: [PATCH 056/342] Add test project --- ModuleManager.sln | 18 +++++- ModuleManagerTests/DummyTest.cs | 14 +++++ ModuleManagerTests/ModuleManagerTests.csproj | 60 +++++++++++++++++++ ModuleManagerTests/Properties/AssemblyInfo.cs | 36 +++++++++++ ModuleManagerTests/packages.config | 5 ++ 5 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 ModuleManagerTests/DummyTest.cs create mode 100644 ModuleManagerTests/ModuleManagerTests.csproj create mode 100644 ModuleManagerTests/Properties/AssemblyInfo.cs create mode 100644 ModuleManagerTests/packages.config diff --git a/ModuleManager.sln b/ModuleManager.sln index 8ffcf7cf..250472d6 100644 --- a/ModuleManager.sln +++ b/ModuleManager.sln @@ -1,8 +1,12 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.12 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleManager", "ModuleManager\ModuleManager.csproj", "{02C8E3AF-69F9-4102-AB60-DD6DE60662D3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleManagerTests", "ModuleManagerTests\ModuleManagerTests.csproj", "{BC2A08C8-64EF-4823-A40B-8889C1CCFD75}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -13,6 +17,16 @@ Global {02C8E3AF-69F9-4102-AB60-DD6DE60662D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {02C8E3AF-69F9-4102-AB60-DD6DE60662D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {02C8E3AF-69F9-4102-AB60-DD6DE60662D3}.Release|Any CPU.Build.0 = Release|Any CPU + {BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4DCC10BA-6036-4DCF-A78B-086DF4487922} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = ModuleManager.csproj diff --git a/ModuleManagerTests/DummyTest.cs b/ModuleManagerTests/DummyTest.cs new file mode 100644 index 00000000..d9fc0624 --- /dev/null +++ b/ModuleManagerTests/DummyTest.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace ModuleManagerTests +{ + public class DummyTest + { + [Fact] + public void PassingTest() + { + Assert.Equal(true, true); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj new file mode 100644 index 00000000..90bd22e3 --- /dev/null +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -0,0 +1,60 @@ + + + + + + Debug + AnyCPU + {BC2A08C8-64EF-4823-A40B-8889C1CCFD75} + Library + Properties + ModuleManagerTests + ModuleManagerTests + v3.5 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + ..\packages\xunit.1.9.2\lib\net20\xunit.dll + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/ModuleManagerTests/Properties/AssemblyInfo.cs b/ModuleManagerTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..fd57fdd5 --- /dev/null +++ b/ModuleManagerTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ModuleManagerTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ModuleManagerTests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bc2a08c8-64ef-4823-a40b-8889c1ccfd75")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config new file mode 100644 index 00000000..decad76a --- /dev/null +++ b/ModuleManagerTests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From e8159e1f9d8f8c292c827dc0e47f1b93431efcb2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 21:47:15 -0700 Subject: [PATCH 057/342] Add MM, Assembly-CSharp, UnityEngine refs --- ModuleManagerTests/ModuleManagerTests.csproj | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 90bd22e3..dc944d9b 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -33,12 +33,14 @@ 4 + + ..\packages\xunit.1.9.2\lib\net20\xunit.dll @@ -50,6 +52,15 @@ + + + + + + {02c8e3af-69f9-4102-ab60-dd6de60662d3} + ModuleManager + + @@ -57,4 +68,4 @@ - \ No newline at end of file + From 563a9427e4b3cd21da3ccaa3c38b149c2bb1ee9c Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 21:56:25 -0700 Subject: [PATCH 058/342] Add console runner Will be needed eventually --- ModuleManagerTests/packages.config | 1 + 1 file changed, 1 insertion(+) diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index decad76a..9139e82b 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file From a7f901e4654900cde28f93c15e27154d66a36e30 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 22:16:07 -0700 Subject: [PATCH 059/342] Yo dawg, I heard you like tests --- ModuleManager.sln | 12 ++++ TestUtils/Properties/AssemblyInfo.cs | 36 ++++++++++++ TestUtils/TestUtils.csproj | 46 +++++++++++++++ TestUtilsTests/DummyTest.cs | 14 +++++ TestUtilsTests/Properties/AssemblyInfo.cs | 36 ++++++++++++ TestUtilsTests/TestUtilsTests.csproj | 71 +++++++++++++++++++++++ TestUtilsTests/packages.config | 6 ++ 7 files changed, 221 insertions(+) create mode 100644 TestUtils/Properties/AssemblyInfo.cs create mode 100644 TestUtils/TestUtils.csproj create mode 100644 TestUtilsTests/DummyTest.cs create mode 100644 TestUtilsTests/Properties/AssemblyInfo.cs create mode 100644 TestUtilsTests/TestUtilsTests.csproj create mode 100644 TestUtilsTests/packages.config diff --git a/ModuleManager.sln b/ModuleManager.sln index 250472d6..ac8200bc 100644 --- a/ModuleManager.sln +++ b/ModuleManager.sln @@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleManager", "ModuleMana EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleManagerTests", "ModuleManagerTests\ModuleManagerTests.csproj", "{BC2A08C8-64EF-4823-A40B-8889C1CCFD75}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtils", "TestUtils\TestUtils.csproj", "{20EAAFE6-510D-4374-8D2F-6B52D0178E85}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtilsTests", "TestUtilsTests\TestUtilsTests.csproj", "{E695C11F-4217-4014-9B51-7232A654C205}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +25,14 @@ Global {BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC2A08C8-64EF-4823-A40B-8889C1CCFD75}.Release|Any CPU.Build.0 = Release|Any CPU + {20EAAFE6-510D-4374-8D2F-6B52D0178E85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20EAAFE6-510D-4374-8D2F-6B52D0178E85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20EAAFE6-510D-4374-8D2F-6B52D0178E85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20EAAFE6-510D-4374-8D2F-6B52D0178E85}.Release|Any CPU.Build.0 = Release|Any CPU + {E695C11F-4217-4014-9B51-7232A654C205}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E695C11F-4217-4014-9B51-7232A654C205}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E695C11F-4217-4014-9B51-7232A654C205}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E695C11F-4217-4014-9B51-7232A654C205}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TestUtils/Properties/AssemblyInfo.cs b/TestUtils/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..4ae86d72 --- /dev/null +++ b/TestUtils/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestUtils")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestUtils")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("20eaafe6-510d-4374-8d2f-6b52d0178e85")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TestUtils/TestUtils.csproj b/TestUtils/TestUtils.csproj new file mode 100644 index 00000000..47b4edb9 --- /dev/null +++ b/TestUtils/TestUtils.csproj @@ -0,0 +1,46 @@ + + + + + Debug + AnyCPU + {20EAAFE6-510D-4374-8D2F-6B52D0178E85} + Library + Properties + TestUtils + TestUtils + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + diff --git a/TestUtilsTests/DummyTest.cs b/TestUtilsTests/DummyTest.cs new file mode 100644 index 00000000..28c10410 --- /dev/null +++ b/TestUtilsTests/DummyTest.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace TestUtilsTests +{ + public class DummyTest + { + [Fact] + public void PassingTest() + { + Assert.Equal(true, true); + } + } +} diff --git a/TestUtilsTests/Properties/AssemblyInfo.cs b/TestUtilsTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..054a6d53 --- /dev/null +++ b/TestUtilsTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestUtilsTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestUtilsTests")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e695c11f-4217-4014-9b51-7232a654c205")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj new file mode 100644 index 00000000..da9a4469 --- /dev/null +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -0,0 +1,71 @@ + + + + + + Debug + AnyCPU + {E695C11F-4217-4014-9B51-7232A654C205} + Library + Properties + TestUtilsTests + TestUtilsTests + v3.5 + 512 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + ..\packages\xunit.1.9.2\lib\net20\xunit.dll + + + + + + + + + + + + {20eaafe6-510d-4374-8d2f-6b52d0178e85} + TestUtils + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/TestUtilsTests/packages.config b/TestUtilsTests/packages.config new file mode 100644 index 00000000..9139e82b --- /dev/null +++ b/TestUtilsTests/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From be37f18a9cff44b889ecbc150b1a9b164b098c66 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 22:23:22 -0700 Subject: [PATCH 060/342] Add TestConfigNode class Makes testing with ConfigNodes by simplifying creating them --- TestUtils/TestConfigNode.cs | 17 ++++++++ TestUtils/TestUtils.csproj | 3 +- TestUtilsTests/TestConfigNodeTest.cs | 62 ++++++++++++++++++++++++++++ TestUtilsTests/TestUtilsTests.csproj | 1 + 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 TestUtils/TestConfigNode.cs create mode 100644 TestUtilsTests/TestConfigNodeTest.cs diff --git a/TestUtils/TestConfigNode.cs b/TestUtils/TestConfigNode.cs new file mode 100644 index 00000000..e7612955 --- /dev/null +++ b/TestUtils/TestConfigNode.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections; + +namespace TestUtils +{ + public class TestConfigNode : ConfigNode, IEnumerable + { + public TestConfigNode() : base() { } + public TestConfigNode(string name) : base(name) { } + + public void Add(string name, string value) => AddValue(name, value); + public void Add(string name, ConfigNode node) => AddNode(name, node); + public void Add(ConfigNode node) => AddNode(node); + + public IEnumerator GetEnumerator() => throw new NotImplementedException(); + } +} diff --git a/TestUtils/TestUtils.csproj b/TestUtils/TestUtils.csproj index 47b4edb9..e8930d4d 100644 --- a/TestUtils/TestUtils.csproj +++ b/TestUtils/TestUtils.csproj @@ -41,6 +41,7 @@ + - + \ No newline at end of file diff --git a/TestUtilsTests/TestConfigNodeTest.cs b/TestUtilsTests/TestConfigNodeTest.cs new file mode 100644 index 00000000..872b4bbb --- /dev/null +++ b/TestUtilsTests/TestConfigNodeTest.cs @@ -0,0 +1,62 @@ +using System; +using Xunit; +using TestUtils; + +namespace TestUtilsTests +{ + public class TestConfigNodeTest + { + [Fact] + public void TestTestConfigNode() + { + ConfigNode node = new TestConfigNode("NODE") + { + { "value1", "something" }, + { "value2", "something else" }, + { "multiple", "first" }, + { "multiple", "second" }, + { "NODE_1", new TestConfigNode + { + { "name", "something" }, + { "stuff", "something else" }, + } + }, + new TestConfigNode("MULTIPLE") + { + { "value3", "blah" }, + { "value4", "bleh" }, + }, + new TestConfigNode("MULTIPLE") + { + { "value3", "blih" }, + { "value4", "bloh" }, + }, + }; + + Assert.Equal("something", node.GetValue("value1")); + Assert.Equal("something else", node.GetValue("value2")); + Assert.Equal(new[] { "first", "second" }, node.GetValues("multiple")); + + ConfigNode innerNode1 = node.GetNode("NODE_1"); + Assert.NotNull(innerNode1); + + Assert.Equal("NODE_1", innerNode1.name); + Assert.Equal("something", innerNode1.GetValue("name")); + Assert.Equal("something else", innerNode1.GetValue("stuff")); + + ConfigNode[] innerNodes2 = node.GetNodes("MULTIPLE"); + Assert.NotNull(innerNodes2); + Assert.Equal(2, innerNodes2.Length); + + ConfigNode innerNode2a = innerNodes2[0]; + Assert.NotNull(innerNode2a); + Assert.Equal("blah", innerNode2a.GetValue("value3")); + Assert.Equal("bleh", innerNode2a.GetValue("value4")); + + ConfigNode innerNode2b = innerNodes2[1]; + Assert.NotNull(innerNode2b); + Assert.Equal("blih", innerNode2b.GetValue("value3")); + Assert.Equal("bloh", innerNode2b.GetValue("value4")); + } + } +} diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index da9a4469..838de7fb 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -48,6 +48,7 @@ + From 278dad81e20c01e24505d4b9a48bd6c083eab828 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Sep 2017 22:24:32 -0700 Subject: [PATCH 061/342] Reference TestUtils --- ModuleManagerTests/ModuleManagerTests.csproj | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index dc944d9b..9cb9cba7 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -60,6 +60,10 @@ {02c8e3af-69f9-4102-ab60-dd6de60662d3} ModuleManager + + {20eaafe6-510d-4374-8d2f-6b52d0178e85} + TestUtils + @@ -68,4 +72,4 @@ - + \ No newline at end of file From 66e6612df80ebeac9c8cd23e37430ad527014a96 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Sep 2017 09:41:02 -0700 Subject: [PATCH 062/342] Don't reference a specific version of System --- ModuleManager/ModuleManager.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 4e475df1..92a34368 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -53,9 +53,7 @@ False False - - False - + False From df9c8ec4ead848c8ec2c7adeb13c7de8a8180477 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Sep 2017 09:42:29 -0700 Subject: [PATCH 063/342] Add test for ImmutableStack --- .../Collections/ImmutableStackTest.cs | 140 ++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 2 files changed, 141 insertions(+) create mode 100644 ModuleManagerTests/Collections/ImmutableStackTest.cs diff --git a/ModuleManagerTests/Collections/ImmutableStackTest.cs b/ModuleManagerTests/Collections/ImmutableStackTest.cs new file mode 100644 index 00000000..56265d45 --- /dev/null +++ b/ModuleManagerTests/Collections/ImmutableStackTest.cs @@ -0,0 +1,140 @@ +using System; +using System.Linq; +using Xunit; + +using ModuleManager.Collections; + +namespace ModuleManagerTests.Collections +{ + public class ImmutableStackTest + { + [Fact] + public void TestValue() + { + object obj = new object(); + ImmutableStack stack = new ImmutableStack(obj); + + Assert.Same(obj, stack.value); + } + + [Fact] + public void TestIsRoot() + { + ImmutableStack stack1 = new ImmutableStack(new object()); + ImmutableStack stack2 = stack1.Push(new object()); + + Assert.True(stack1.IsRoot); + Assert.False(stack2.IsRoot); + } + + [Fact] + public void TestRoot() + { + ImmutableStack stack1 = new ImmutableStack(new object()); + ImmutableStack stack2 = stack1.Push(new object()); + ImmutableStack stack3 = stack2.Push(new object()); + + Assert.Same(stack1, stack1.Root); + Assert.Same(stack1, stack2.Root); + Assert.Same(stack1, stack3.Root); + } + + [Fact] + public void TestDepth() + { + ImmutableStack stack1 = new ImmutableStack(new object()); + ImmutableStack stack2 = stack1.Push(new object()); + ImmutableStack stack3 = stack2.Push(new object()); + + Assert.Equal(1, stack1.Depth); + Assert.Equal(2, stack2.Depth); + Assert.Equal(3, stack3.Depth); + } + + [Fact] + public void TestPush() + { + object obj1 = new object(); + object obj2 = new object(); + object obj3 = new object(); + ImmutableStack stack1 = new ImmutableStack(obj1); + ImmutableStack stack2 = stack1.Push(obj2); + ImmutableStack stack3 = stack2.Push(obj3); + + Assert.Same(stack2, stack3.parent); + Assert.Same(stack1, stack2.parent); + + Assert.Same(obj1, stack1.value); + Assert.Same(obj2, stack2.value); + Assert.Same(obj3, stack3.value); + } + + [Fact] + public void TestPop() + { + object obj1 = new object(); + object obj2 = new object(); + object obj3 = new object(); + ImmutableStack stack = new ImmutableStack(obj1).Push(obj2).Push(obj3); + + Assert.Same(obj1, stack.Pop().Pop().value); + Assert.Same(obj2, stack.Pop().value); + Assert.Same(obj3, stack.value); + } + + [Fact] + public void TestPop__Root() + { + ImmutableStack stack = new ImmutableStack(new object()); + + Assert.Throws(delegate + { + stack.Pop(); + }); + } + + [Fact] + public void TestReplaceValue() + { + object obj1 = new object(); + object obj2 = new object(); + object obj3 = new object(); + ImmutableStack stack1 = new ImmutableStack(obj1); + ImmutableStack stack2 = stack1.Push(obj2); + + ImmutableStack stack3 = stack2.ReplaceValue(obj3); + + Assert.Same(obj3, stack3.value); + Assert.Same(stack1, stack3.parent); + } + + [Fact] + public void TestReplaceValue__Root() + { + object obj1 = new object(); + object obj2 = new object(); + ImmutableStack stack1 = new ImmutableStack(obj1); + + ImmutableStack stack2 = stack1.ReplaceValue(obj2); + + Assert.Same(obj2, stack2.value); + Assert.Null(stack2.parent); + } + + [Fact] + public void TestEnumerator() + { + object obj1 = new object(); + object obj2 = new object(); + object obj3 = new object(); + ImmutableStack stack = new ImmutableStack(obj1).Push(obj2).Push(obj3); + + object[] objs = stack.ToArray(); + + Assert.Equal(3, objs.Length); + Assert.Same(obj3, objs[0]); + Assert.Same(obj2, objs[1]); + Assert.Same(obj1, objs[2]); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 9cb9cba7..93c98364 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -46,6 +46,7 @@ + From 3e7100991cc45ea01bc78ee2f95f4cdd38cae0a6 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Sep 2017 16:05:33 -0700 Subject: [PATCH 064/342] Add test for GetPath --- .../Extensions/NodeStackExtensionsTest.cs | 22 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 2 files changed, 23 insertions(+) create mode 100644 ModuleManagerTests/Extensions/NodeStackExtensionsTest.cs diff --git a/ModuleManagerTests/Extensions/NodeStackExtensionsTest.cs b/ModuleManagerTests/Extensions/NodeStackExtensionsTest.cs new file mode 100644 index 00000000..2539d10b --- /dev/null +++ b/ModuleManagerTests/Extensions/NodeStackExtensionsTest.cs @@ -0,0 +1,22 @@ +using System; +using Xunit; +using ModuleManager.Collections; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class NodeStackExtensionsTest + { + [Fact] + public void TestGetPath() + { + ConfigNode node1 = new ConfigNode("NODE1"); + ConfigNode node2 = new ConfigNode("NODE2"); + ConfigNode node3 = new ConfigNode("NODE3"); + + ImmutableStack stack = new ImmutableStack(node1).Push(node2).Push(node3); + + Assert.Equal("NODE1/NODE2/NODE3", stack.GetPath()); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 93c98364..a811f649 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -48,6 +48,7 @@ + From 6e8285c5c6559339826804e599b90c3883284c76 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Sep 2017 18:35:06 -0700 Subject: [PATCH 065/342] Add NSubstitute --- ModuleManagerTests/ModuleManagerTests.csproj | 3 +++ ModuleManagerTests/packages.config | 1 + 2 files changed, 4 insertions(+) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index a811f649..b2145e56 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -34,6 +34,9 @@ + + ..\packages\NSubstitute.2.0.3\lib\net35\NSubstitute.dll + diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index 9139e82b..ec095fab 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -1,5 +1,6 @@  + From 7398d4d1acbf2a8ed1db9dbc372fa94cf2c9cfa6 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Sep 2017 18:53:52 -0700 Subject: [PATCH 066/342] Add tests for ModLogger --- ModuleManagerTests/Logging/ModLoggerTest.cs | 63 ++++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 2 files changed, 64 insertions(+) create mode 100644 ModuleManagerTests/Logging/ModLoggerTest.cs diff --git a/ModuleManagerTests/Logging/ModLoggerTest.cs b/ModuleManagerTests/Logging/ModLoggerTest.cs new file mode 100644 index 00000000..4bd48fc5 --- /dev/null +++ b/ModuleManagerTests/Logging/ModLoggerTest.cs @@ -0,0 +1,63 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class ModLoggerTest + { + private ILogger innerLogger; + private ModLogger logger; + + public ModLoggerTest() + { + innerLogger = Substitute.For(); + logger = new ModLogger("MyMod", innerLogger); + } + [Fact] + public void TestLog() + { + logger.Log(LogType.Log, "this is a log message"); + logger.Log(LogType.Error, "this is another log message"); + + innerLogger.Received().Log(LogType.Log, "[MyMod] this is a log message"); + innerLogger.Received().Log(LogType.Error, "[MyMod] this is another log message"); + } + + [Fact] + public void TestInfo() + { + logger.Info("well hi there"); + + innerLogger.Received().Log(LogType.Log, "[MyMod] well hi there"); + } + + [Fact] + public void TestWarning() + { + logger.Warning("I'm warning you"); + + innerLogger.Received().Log(LogType.Warning, "[MyMod] I'm warning you"); + } + + [Fact] + public void TestError() + { + logger.Error("You have made a grave mistake"); + + innerLogger.Received().Log(LogType.Error, "[MyMod] You have made a grave mistake"); + } + + [Fact] + public void TestException() + { + Exception e = new Exception(); + logger.Exception("An exception was thrown", e); + + innerLogger.Received().Log(LogType.Error, "[MyMod] An exception was thrown"); + innerLogger.Received().LogException(e); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index b2145e56..f3b916c9 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -52,6 +52,7 @@ + From a5a095fc17b5e3a8c8b9c756a035d0bcbefdbec2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Sep 2017 18:59:32 -0700 Subject: [PATCH 067/342] Fix an error --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 5e503a08..f1bde625 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -874,7 +874,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context) else { needsCopy = true; - progress.NeedsUnsatisfiedNode(context.patchUrl.url, stack.Push(node).GetPath()); + progress.NeedsUnsatisfiedNode(context.patchUrl.parent.url, stack.Push(node).GetPath()); } } catch (ArgumentOutOfRangeException e) From 564b226028542cf258b2792fb8612baab6b939dc Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 3 Sep 2017 19:39:57 -0700 Subject: [PATCH 068/342] Add UrlBuilder Hackily creates UrlDir, UrlFile, UrlConfig for testing purposes --- TestUtils/TestUtils.csproj | 1 + TestUtils/URLBuilder.cs | 116 ++++++++++++ TestUtilsTests/TestUtilsTests.csproj | 1 + TestUtilsTests/UrlBuilderTest.cs | 259 +++++++++++++++++++++++++++ 4 files changed, 377 insertions(+) create mode 100644 TestUtils/URLBuilder.cs create mode 100644 TestUtilsTests/UrlBuilderTest.cs diff --git a/TestUtils/TestUtils.csproj b/TestUtils/TestUtils.csproj index e8930d4d..06ab8be0 100644 --- a/TestUtils/TestUtils.csproj +++ b/TestUtils/TestUtils.csproj @@ -42,6 +42,7 @@ + \ No newline at end of file diff --git a/TestUtils/URLBuilder.cs b/TestUtils/URLBuilder.cs new file mode 100644 index 00000000..5253d158 --- /dev/null +++ b/TestUtils/URLBuilder.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace TestUtils +{ + public static class UrlBuilder + { + private static FieldInfo UrlDir__field__name; + private static FieldInfo UrlDir__field__root; + private static FieldInfo UrlDir__field__parent; + + private static FieldInfo UrlFile__field__name; + private static FieldInfo UrlFile__field__fileType; + private static FieldInfo UrlFile__field__fileExtension; + + static UrlBuilder() + { + FieldInfo[] UrlDirFields = typeof(UrlDir).GetFields(BindingFlags.Instance | BindingFlags.NonPublic); + FieldInfo[] UrlDirFields__string = UrlDirFields.Where(field => field.FieldType == typeof(string)).ToArray(); + FieldInfo[] UrlDirFields__UrlDir = UrlDirFields.Where(field => field.FieldType == typeof(UrlDir)).ToArray(); + + UrlDir__field__name = UrlDirFields__string[0]; + UrlDir__field__root = UrlDirFields__UrlDir[0]; + UrlDir__field__parent = UrlDirFields__UrlDir[1]; + + FieldInfo[] UrlFileFields = typeof(UrlDir.UrlFile).GetFields(BindingFlags.Instance | BindingFlags.NonPublic); + FieldInfo[] UrlFileFields__string = UrlFileFields.Where(field => field.FieldType == typeof(string)).ToArray(); + FieldInfo[] UrlFileFields__FileType = UrlFileFields.Where(field => field.FieldType == typeof(UrlDir.FileType)).ToArray(); + + UrlFile__field__name = UrlFileFields__string[0]; + UrlFile__field__fileExtension = UrlFileFields__string[2]; + UrlFile__field__fileType = UrlFileFields__FileType[0]; + } + + public static UrlDir CreateRoot() + { + return new UrlDir(new UrlDir.ConfigDirectory[0], new UrlDir.ConfigFileType[0]); + } + + public static UrlDir CreateDir(string url, UrlDir parent = null) + { + if (parent == null) parent = CreateRoot(); + + UrlDir current = parent; + + foreach(string name in url.Split('/')) + { + UrlDir dir = CreateRoot(); + UrlDir__field__name.SetValue(dir, name); + UrlDir__field__root.SetValue(dir, current.root); + UrlDir__field__parent.SetValue(dir, current); + + current.children.Add(dir); + current = dir; + } + + return current; + } + + public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) + { + int sepIndex = path.LastIndexOf('/'); + string name = path; + + if (sepIndex != -1) + { + parent = CreateDir(path.Substring(0, sepIndex), parent); + name = path.Substring(sepIndex + 1); + } + else if (parent == null) + { + parent = CreateRoot(); + } + + bool cfg = false; + string newName = name; + + if (Path.GetExtension(name) == ".cfg") + { + cfg = true; + newName = name + ".not_cfg"; + } + + UrlDir.UrlFile file = new UrlDir.UrlFile(parent, new FileInfo(newName)); + + if (cfg) + { + UrlFile__field__name.SetValue(file, Path.GetFileNameWithoutExtension(name)); + UrlFile__field__fileExtension.SetValue(file, "cfg"); + UrlFile__field__fileType.SetValue(file, UrlDir.FileType.Config); + } + + parent.files.Add(file); + + return file; + } + + public static UrlDir.UrlConfig CreateConfig(ConfigNode node, UrlDir.UrlFile parent) + { + UrlDir.UrlConfig config = new UrlDir.UrlConfig(parent, node); + parent.configs.Add(config); + return config; + } + + public static UrlDir.UrlConfig CreateConfig(string url, ConfigNode node, UrlDir parent = null) + { + if (Path.GetExtension(url) != ".cfg") url += ".cfg"; + + UrlDir.UrlFile file = CreateFile(url, parent); + + return CreateConfig(node, file); + } + } +} diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 838de7fb..93310818 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -49,6 +49,7 @@ + diff --git a/TestUtilsTests/UrlBuilderTest.cs b/TestUtilsTests/UrlBuilderTest.cs new file mode 100644 index 00000000..06190441 --- /dev/null +++ b/TestUtilsTests/UrlBuilderTest.cs @@ -0,0 +1,259 @@ +using System; +using Xunit; +using TestUtils; + +namespace TestUtilsTests +{ + public class UrlBuilderTest + { + [Fact] + public void TestCreateRoot() + { + UrlDir root = UrlBuilder.CreateRoot(); + + Assert.Equal("root", root.name); + Assert.Null(root.parent); + Assert.Same(root, root.root); + } + + [Fact] + public void TestCreateDir() + { + UrlDir dir = UrlBuilder.CreateDir("abc"); + + Assert.Equal("abc", dir.name); + + UrlDir root = dir.parent; + Assert.NotNull(root); + Assert.Equal("root", root.name); + Assert.Null(root.parent); + Assert.Contains(dir, root.children); + + Assert.Same(root, dir.root); + Assert.Same(root, root.root); + } + + [Fact] + public void TestCreateDir__Parent() + { + UrlDir root = UrlBuilder.CreateRoot(); + + UrlDir child1 = UrlBuilder.CreateDir("child1", root); + + Assert.Equal("child1", child1.name); + Assert.Same(root, child1.parent); + Assert.Same(root, child1.root); + + Assert.Equal("child1", child1.url); + + Assert.Contains(child1, root.children); + + UrlDir child2 = UrlBuilder.CreateDir("child2", child1); + + Assert.Equal("child2", child2.name); + Assert.Same(child1, child2.parent); + Assert.Same(root, child2.root); + + Assert.Equal("child1/child2", child2.url); + + Assert.Contains(child2, child1.children); + } + + [Fact] + public void TestCreateDir__Url() + { + UrlDir dir = UrlBuilder.CreateDir("abc/def"); + + Assert.Equal("def", dir.name); + + UrlDir parent = dir.parent; + + Assert.NotNull(parent); + Assert.Equal("abc", parent.name); + Assert.Contains(dir, parent.children); + + UrlDir root = parent.parent; + + Assert.NotNull(root); + Assert.Equal("root", root.name); + Assert.Contains(parent, root.children); + Assert.Null(root.parent); + + Assert.Same(root, root.root); + Assert.Same(root, parent.root); + Assert.Same(root, dir.root); + } + + [Fact] + public void TestCreateDir__Url__Parent() + { + UrlDir root = UrlBuilder.CreateRoot(); + UrlDir parent1 = UrlBuilder.CreateDir("abc", root); + + UrlDir dir = UrlBuilder.CreateDir("def/ghi", parent1); + + Assert.Equal("ghi", dir.name); + + UrlDir parent2 = dir.parent; + + Assert.NotNull(parent2); + Assert.Equal("def", parent2.name); + Assert.Contains(dir, parent2.children); + + Assert.Same(parent1, parent2.parent); + Assert.Contains(parent2, parent1.children); + + Assert.Same(root, dir.root); + Assert.Same(root, parent2.root); + } + + [Fact] + public void TestCreateFile() + { + UrlDir.UrlFile file = UrlBuilder.CreateFile("someFile.txt"); + + Assert.Equal("someFile", file.name); + Assert.Equal("txt", file.fileExtension); + Assert.Equal(UrlDir.FileType.Unknown, file.fileType); + + UrlDir root = file.parent; + Assert.NotNull(root); + Assert.Equal("root", root.name); + Assert.Null(root.parent); + Assert.Contains(file, root.files); + Assert.Same(root, file.root); + } + + [Fact] + public void TestCreateFile__Parent() + { + UrlDir root = UrlBuilder.CreateRoot(); + UrlDir dir = UrlBuilder.CreateDir("someDir", root); + UrlDir.UrlFile file = UrlBuilder.CreateFile("someFile.txt", dir); + + Assert.Equal("someFile", file.name); + Assert.Equal("txt", file.fileExtension); + Assert.Equal(UrlDir.FileType.Unknown, file.fileType); + Assert.Same(dir, file.parent); + Assert.Same(root, file.root); + + Assert.Equal("someDir/someFile", file.url); + Assert.Contains(file, dir.files); + Assert.Contains(file, root.AllFiles); + } + + // KSP tries to load .cfg files so need to have special handling + [Fact] + public void TestCreateFile__cfg() + { + UrlDir root = UrlBuilder.CreateRoot(); + UrlDir dir = UrlBuilder.CreateDir("someDir", root); + UrlDir.UrlFile file = UrlBuilder.CreateFile("someFile.cfg", dir); + + Assert.Equal("someFile", file.name); + Assert.Equal("cfg", file.fileExtension); + Assert.Equal(UrlDir.FileType.Config, file.fileType); + Assert.Same(dir, file.parent); + Assert.Same(root, file.root); + + Assert.Equal("someDir/someFile", file.url); + Assert.Contains(file, dir.files); + Assert.Contains(file, root.AllConfigFiles); + } + + [Fact] + public void TestCreateFile__Url() + { + UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def/ghi.txt"); + + Assert.Equal("ghi", file.name); + Assert.Equal("txt", file.fileExtension); + Assert.Equal(UrlDir.FileType.Unknown, file.fileType); + Assert.Equal("abc/def/ghi", file.url); + + UrlDir parent1 = file.parent; + Assert.NotNull(parent1); + Assert.Equal("def", parent1.name); + Assert.Contains(file, parent1.files); + + UrlDir parent2 = parent1.parent; + Assert.NotNull(parent2); + Assert.Equal("abc", parent2.name); + Assert.Contains(parent1, parent2.children); + + UrlDir root = parent2.parent; + Assert.NotNull(root); + Assert.Equal("root", root.name); + Assert.Contains(parent2, root.children); + Assert.Null(root.parent); + + Assert.Same(root, file.root); + Assert.Same(root, parent1.root); + Assert.Same(root, parent2.root); + Assert.Same(root, root.root); + + Assert.Contains(file, root.AllFiles); + Assert.Contains(file, parent2.AllFiles); + } + + [Fact] + public void TestCreateConfig() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "name", "blah" }, + { "foo", "bar" }, + }; + UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + UrlDir.UrlConfig config = UrlBuilder.CreateConfig(node, file); + + Assert.Equal("SOME_NODE", config.type); + Assert.Equal("blah", config.name); + Assert.Same(node, config.config); + Assert.Equal("abc/def/blah", config.url); // I don't know why this is correct, but it is + Assert.Same(file, config.parent); + Assert.Contains(config, file.configs); + } + + [Fact] + public void TestCreateConfig__Url() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "name", "blah" }, + { "foo", "bar" }, + }; + UrlDir.UrlConfig config = UrlBuilder.CreateConfig("abc/def", node); + + Assert.Equal("SOME_NODE", config.type); + Assert.Equal("blah", config.name); + Assert.Same(node, config.config); + Assert.Equal("abc/def/blah", config.url); + + UrlDir.UrlFile file = config.parent; + Assert.NotNull(file); + Assert.Equal("def", file.name); + Assert.Equal("cfg", file.fileExtension); + Assert.Equal(UrlDir.FileType.Config, file.fileType); + Assert.Contains(config, file.configs); + + UrlDir parent = file.parent; + Assert.NotNull(parent); + Assert.Equal("abc", parent.name); + Assert.Contains(file, parent.files); + + UrlDir root = parent.parent; + Assert.NotNull(root); + Assert.Equal("root", root.name); + Assert.Contains(parent, root.children); + Assert.Null(root.parent); + + Assert.Same(root, file.root); + Assert.Same(root, parent.root); + Assert.Same(root, root.root); + + Assert.Contains(config, root.AllConfigs); + Assert.Contains(config, root.GetConfigs("SOME_NODE")); + } + } +} From f190d17f56a1837204b62ad004da8005df624c2b Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 3 Sep 2017 21:55:27 -0700 Subject: [PATCH 069/342] Progress shouldn't depend on deleted subnodes The number of needs unsatisfied nodes it should be counting is the number of root nodes that have been removed, not subnodes as well --- ModuleManager/IPatchProgress.cs | 1 + ModuleManager/MMPatchLoader.cs | 2 +- ModuleManager/PatchProgress.cs | 13 +++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ModuleManager/IPatchProgress.cs b/ModuleManager/IPatchProgress.cs index 43895e32..253071bf 100644 --- a/ModuleManager/IPatchProgress.cs +++ b/ModuleManager/IPatchProgress.cs @@ -17,6 +17,7 @@ public interface IPatchProgress void Error(UrlDir.UrlConfig url, string message); void Exception(string message, Exception exception); void Exception(UrlDir.UrlConfig url, string message, Exception exception); + void NeedsUnsatisfiedRoot(string url, string name); void NeedsUnsatisfiedNode(string url, string path); void NeedsUnsatisfiedValue(string url, string path, string valName); void ApplyingCopy(string url, string patchUrl); diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index f1bde625..c133d68d 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -798,7 +798,7 @@ private void CheckNeeds() if (!CheckNeeds(ref type)) { - progress.NeedsUnsatisfiedNode(currentMod.parent.url, currentMod.type); + progress.NeedsUnsatisfiedRoot(currentMod.parent.url, currentMod.type); continue; } diff --git a/ModuleManager/PatchProgress.cs b/ModuleManager/PatchProgress.cs index e4d3f6a2..9cb34f6c 100644 --- a/ModuleManager/PatchProgress.cs +++ b/ModuleManager/PatchProgress.cs @@ -16,6 +16,8 @@ public class PatchProgress : IPatchProgress public int ExceptionCount { get; private set; } = 0; + public int NeedsUnsatisfiedRootCount { get; private set; } = 0; + public int NeedsUnsatisfiedCount { get; private set; } = 0; public Dictionary ErrorFiles { get; } = new Dictionary(); @@ -27,7 +29,7 @@ public float ProgressFraction get { if (TotalPatchCount > 0) - return (AppliedPatchCount + NeedsUnsatisfiedCount) / (float)TotalPatchCount; + return (AppliedPatchCount + NeedsUnsatisfiedRootCount) / (float)TotalPatchCount; return 0; } } @@ -65,9 +67,16 @@ public void PatchApplied() AppliedPatchCount += 1; } + public void NeedsUnsatisfiedRoot(string url, string name) + { + logger.Info($"Deleting root node in {url} subnode: {name} as it can't satisfy its NEEDS"); + NeedsUnsatisfiedCount += 1; + NeedsUnsatisfiedRootCount += 1; + } + public void NeedsUnsatisfiedNode(string url, string path) { - logger.Info($"Deleting Node in file {url} subnode: {path} as it can't satisfy its NEEDS"); + logger.Info($"Deleting node in file {url} subnode: {path} as it can't satisfy its NEEDS"); NeedsUnsatisfiedCount += 1; } From fca971747aa52e91653941cc5988cf7fa8119503 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 4 Sep 2017 14:24:21 -0700 Subject: [PATCH 070/342] These should use actual URLs Since all the calls were just using .url anyway --- ModuleManager/IPatchProgress.cs | 6 +++--- ModuleManager/MMPatchLoader.cs | 6 +++--- ModuleManager/PatchProgress.cs | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ModuleManager/IPatchProgress.cs b/ModuleManager/IPatchProgress.cs index 253071bf..95714b32 100644 --- a/ModuleManager/IPatchProgress.cs +++ b/ModuleManager/IPatchProgress.cs @@ -20,9 +20,9 @@ public interface IPatchProgress void NeedsUnsatisfiedRoot(string url, string name); void NeedsUnsatisfiedNode(string url, string path); void NeedsUnsatisfiedValue(string url, string path, string valName); - void ApplyingCopy(string url, string patchUrl); - void ApplyingDelete(string url, string patchUrl); - void ApplyingUpdate(string url, string patchUrl); + void ApplyingCopy(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); + void ApplyingDelete(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); + void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); void PatchAdded(); void PatchApplied(); } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index c133d68d..8342716d 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1029,7 +1029,7 @@ public IEnumerator ApplyPatch(string Stage) switch (cmd) { case Command.Edit: - progress.ApplyingUpdate(url.url, mod.url); + progress.ApplyingUpdate(url, mod); url.config = ModifyNode(new NodeStack(url.config), mod.config, context); break; @@ -1037,7 +1037,7 @@ public IEnumerator ApplyPatch(string Stage) ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); if (url.config.name != mod.name) { - progress.ApplyingCopy(url.url, mod.url); + progress.ApplyingCopy(url, mod); url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); } else @@ -1048,7 +1048,7 @@ public IEnumerator ApplyPatch(string Stage) break; case Command.Delete: - progress.ApplyingDelete(url.url, mod.url); + progress.ApplyingDelete(url, mod); url.parent.configs.Remove(url); break; diff --git a/ModuleManager/PatchProgress.cs b/ModuleManager/PatchProgress.cs index 9cb34f6c..d7a38ba4 100644 --- a/ModuleManager/PatchProgress.cs +++ b/ModuleManager/PatchProgress.cs @@ -44,21 +44,21 @@ public void PatchAdded() TotalPatchCount += 1; } - public void ApplyingUpdate(string url, string patchUrl) + public void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { - logger.Info($"Applying update {patchUrl} to {url}"); + logger.Info($"Applying update {patch.url} to {original.url}"); PatchedNodeCount += 1; } - public void ApplyingCopy(string url, string patchUrl) + public void ApplyingCopy(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { - logger.Info($"Applying copy {patchUrl} to {url}"); + logger.Info($"Applying copy {patch.url} to {original.url}"); PatchedNodeCount += 1; } - public void ApplyingDelete(string url, string patchUrl) + public void ApplyingDelete(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { - logger.Info($"Applying delete {patchUrl} to {url}"); + logger.Info($"Applying delete {patch.url} to {original.url}"); PatchedNodeCount += 1; } From 28e4ce78e513990712835ae12a6fe7d89f116ff7 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 4 Sep 2017 14:37:19 -0700 Subject: [PATCH 071/342] These too --- ModuleManager/IPatchProgress.cs | 7 ++++--- ModuleManager/MMPatchLoader.cs | 6 +++--- ModuleManager/PatchProgress.cs | 14 ++++++++------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/ModuleManager/IPatchProgress.cs b/ModuleManager/IPatchProgress.cs index 95714b32..e02cd1d0 100644 --- a/ModuleManager/IPatchProgress.cs +++ b/ModuleManager/IPatchProgress.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager { @@ -17,9 +18,9 @@ public interface IPatchProgress void Error(UrlDir.UrlConfig url, string message); void Exception(string message, Exception exception); void Exception(UrlDir.UrlConfig url, string message, Exception exception); - void NeedsUnsatisfiedRoot(string url, string name); - void NeedsUnsatisfiedNode(string url, string path); - void NeedsUnsatisfiedValue(string url, string path, string valName); + void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url); + void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, NodeStack path); + void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, NodeStack path, string valName); void ApplyingCopy(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); void ApplyingDelete(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 8342716d..196398cb 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -798,7 +798,7 @@ private void CheckNeeds() if (!CheckNeeds(ref type)) { - progress.NeedsUnsatisfiedRoot(currentMod.parent.url, currentMod.type); + progress.NeedsUnsatisfiedRoot(currentMod); continue; } @@ -837,7 +837,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context) else { needsCopy = true; - context.progress.NeedsUnsatisfiedValue(context.patchUrl.url, stack.GetPath(), val.name); + context.progress.NeedsUnsatisfiedValue(context.patchUrl, stack, val.name); } } catch (ArgumentOutOfRangeException e) @@ -874,7 +874,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context) else { needsCopy = true; - progress.NeedsUnsatisfiedNode(context.patchUrl.parent.url, stack.Push(node).GetPath()); + progress.NeedsUnsatisfiedNode(context.patchUrl, stack.Push(node)); } } catch (ArgumentOutOfRangeException e) diff --git a/ModuleManager/PatchProgress.cs b/ModuleManager/PatchProgress.cs index d7a38ba4..cb723e73 100644 --- a/ModuleManager/PatchProgress.cs +++ b/ModuleManager/PatchProgress.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using ModuleManager.Extensions; using ModuleManager.Logging; +using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager { @@ -67,22 +69,22 @@ public void PatchApplied() AppliedPatchCount += 1; } - public void NeedsUnsatisfiedRoot(string url, string name) + public void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url) { - logger.Info($"Deleting root node in {url} subnode: {name} as it can't satisfy its NEEDS"); + logger.Info($"Deleting root node in {url.parent.url} subnode: {url.type} as it can't satisfy its NEEDS"); NeedsUnsatisfiedCount += 1; NeedsUnsatisfiedRootCount += 1; } - public void NeedsUnsatisfiedNode(string url, string path) + public void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, NodeStack path) { - logger.Info($"Deleting node in file {url} subnode: {path} as it can't satisfy its NEEDS"); + logger.Info($"Deleting node in file {url.parent.url} subnode: {path.GetPath()} as it can't satisfy its NEEDS"); NeedsUnsatisfiedCount += 1; } - public void NeedsUnsatisfiedValue(string url, string path, string valName) + public void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, NodeStack path, string valName) { - logger.Info($"Deleting value in file {url} subnode: {path} value: {valName} as it can't satisfy its NEEDS"); + logger.Info($"Deleting value in file {url.parent.url} subnode: {path.GetPath()} value: {valName} as it can't satisfy its NEEDS"); NeedsUnsatisfiedCount += 1; } From 8d8463d17795917e4906dd469cae52e53c83bd60 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 4 Sep 2017 21:51:40 -0700 Subject: [PATCH 072/342] Minor logging tweak --- ModuleManager/PatchProgress.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/PatchProgress.cs b/ModuleManager/PatchProgress.cs index cb723e73..212f612f 100644 --- a/ModuleManager/PatchProgress.cs +++ b/ModuleManager/PatchProgress.cs @@ -71,7 +71,7 @@ public void PatchApplied() public void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url) { - logger.Info($"Deleting root node in {url.parent.url} subnode: {url.type} as it can't satisfy its NEEDS"); + logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its NEEDS"); NeedsUnsatisfiedCount += 1; NeedsUnsatisfiedRootCount += 1; } From d37f58bc70332b6e5271fe69303d78c3aeb2b741 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 4 Sep 2017 21:51:49 -0700 Subject: [PATCH 073/342] Add tests for PatchProgress --- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PatchProgressTest.cs | 213 +++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 ModuleManagerTests/PatchProgressTest.cs diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index f3b916c9..df8d088e 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -53,6 +53,7 @@ + diff --git a/ModuleManagerTests/PatchProgressTest.cs b/ModuleManagerTests/PatchProgressTest.cs new file mode 100644 index 00000000..60304d3a --- /dev/null +++ b/ModuleManagerTests/PatchProgressTest.cs @@ -0,0 +1,213 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; +using NodeStack = ModuleManager.Collections.ImmutableStack; + +namespace ModuleManagerTests +{ + public class PatchProgressTest + { + private IBasicLogger logger; + private PatchProgress progress; + + public PatchProgressTest() + { + logger = Substitute.For(); + progress = new PatchProgress(logger); + } + + [Fact] + public void TestPatchAdded() + { + Assert.Equal(0, progress.TotalPatchCount); + progress.PatchAdded(); + Assert.Equal(1, progress.TotalPatchCount); + progress.PatchAdded(); + Assert.Equal(2, progress.TotalPatchCount); + } + + [Fact] + public void TestApplyingUpdate() + { + UrlDir.UrlConfig original = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("@SOME_NODE")); + UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("@SOME_NODE")); + + Assert.Equal(0, progress.PatchedNodeCount); + + progress.ApplyingUpdate(original, patch1); + Assert.Equal(1, progress.PatchedNodeCount); + logger.Received().Info("Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); + + progress.ApplyingUpdate(original, patch2); + Assert.Equal(2, progress.PatchedNodeCount); + logger.Received().Info("Applying update pqr/stu/@SOME_NODE to abc/def/SOME_NODE"); + } + + [Fact] + public void TesApplyingCopy() + { + UrlDir.UrlConfig original = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("+SOME_NODE")); + UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("+SOME_NODE")); + + Assert.Equal(0, progress.PatchedNodeCount); + + progress.ApplyingCopy(original, patch1); + Assert.Equal(1, progress.PatchedNodeCount); + logger.Received().Info("Applying copy ghi/jkl/+SOME_NODE to abc/def/SOME_NODE"); + + progress.ApplyingCopy(original, patch2); + Assert.Equal(2, progress.PatchedNodeCount); + logger.Received().Info("Applying copy pqr/stu/+SOME_NODE to abc/def/SOME_NODE"); + } + + [Fact] + public void TesApplyingDelete() + { + UrlDir.UrlConfig original = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!SOME_NODE")); + UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("!SOME_NODE")); + + Assert.Equal(0, progress.PatchedNodeCount); + + progress.ApplyingDelete(original, patch1); + Assert.Equal(1, progress.PatchedNodeCount); + logger.Received().Info("Applying delete ghi/jkl/!SOME_NODE to abc/def/SOME_NODE"); + + progress.ApplyingDelete(original, patch2); + Assert.Equal(2, progress.PatchedNodeCount); + logger.Received().Info("Applying delete pqr/stu/!SOME_NODE to abc/def/SOME_NODE"); + } + + [Fact] + public void TestPatchApplied() + { + Assert.Equal(0, progress.AppliedPatchCount); + progress.PatchApplied(); + Assert.Equal(1, progress.AppliedPatchCount); + progress.PatchApplied(); + Assert.Equal(2, progress.AppliedPatchCount); + } + + [Fact] + public void TestNeedsUnsatisfiedRoot() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); + + Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.NeedsUnsatisfiedRootCount); + + progress.NeedsUnsatisfiedRoot(config1); + Assert.Equal(1, progress.NeedsUnsatisfiedCount); + Assert.Equal(1, progress.NeedsUnsatisfiedRootCount); + logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); + + progress.NeedsUnsatisfiedRoot(config2); + Assert.Equal(2, progress.NeedsUnsatisfiedCount); + Assert.Equal(2, progress.NeedsUnsatisfiedRootCount); + logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); + } + + [Fact] + public void TestNeedsUnsatisfiedNode() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + NodeStack stack1 = new NodeStack(config1.config).Push(new ConfigNode("SOME_CHILD_NODE")); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); + NodeStack stack2 = new NodeStack(config2.config).Push(new ConfigNode("SOME_OTHER_CHILD_NODE")); + + Assert.Equal(0, progress.NeedsUnsatisfiedCount); + + progress.NeedsUnsatisfiedNode(config1, stack1); + Assert.Equal(1, progress.NeedsUnsatisfiedCount); + logger.Received().Info("Deleting node in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE as it can't satisfy its NEEDS"); + + progress.NeedsUnsatisfiedNode(config2, stack2); + Assert.Equal(2, progress.NeedsUnsatisfiedCount); + logger.Received().Info("Deleting node in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); + } + + [Fact] + public void TestNeedsUnsatisfiedValue() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + NodeStack stack1 = new NodeStack(config1.config).Push(new ConfigNode("SOME_CHILD_NODE")); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); + NodeStack stack2 = new NodeStack(config2.config).Push(new ConfigNode("SOME_OTHER_CHILD_NODE")); + + Assert.Equal(0, progress.NeedsUnsatisfiedCount); + + progress.NeedsUnsatisfiedValue(config1, stack1, "some_value"); + Assert.Equal(1, progress.NeedsUnsatisfiedCount); + logger.Received().Info("Deleting value in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE value: some_value as it can't satisfy its NEEDS"); + + progress.NeedsUnsatisfiedValue(config2, stack2, "some_other_value"); + Assert.Equal(2, progress.NeedsUnsatisfiedCount); + logger.Received().Info("Deleting value in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE value: some_other_value as it can't satisfy its NEEDS"); + } + + [Fact] + public void TestError() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_OTHER_NODE")); + + Assert.Equal(0, progress.ErrorCount); + Assert.False(progress.ErrorFiles.ContainsKey("abc/def.cfg")); + + progress.Error(config1, "An error message no one is going to read"); + Assert.Equal(1, progress.ErrorCount); + Assert.Equal(1, progress.ErrorFiles["abc/def.cfg"]); + logger.Received().Error("An error message no one is going to read"); + + progress.Error(config2, "Maybe someone will read this one"); + Assert.Equal(2, progress.ErrorCount); + Assert.Equal(2, progress.ErrorFiles["abc/def.cfg"]); + logger.Received().Error("Maybe someone will read this one"); + } + + [Fact] + public void TestException() + { + Exception e1 = new Exception(); + Exception e2 = new Exception(); + + Assert.Equal(0, progress.ExceptionCount); + + progress.Exception("An exception was thrown", e1); + Assert.Equal(1, progress.ExceptionCount); + logger.Received().Exception("An exception was thrown", e1); + + progress.Exception("An exception was tossed", e2); + Assert.Equal(2, progress.ExceptionCount); + logger.Received().Exception("An exception was tossed", e2); + } + + [Fact] + public void TestException__Url() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + Exception e1 = new Exception(); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_OTHER_NODE")); + Exception e2 = new Exception(); + + Assert.Equal(0, progress.ExceptionCount); + Assert.False(progress.ErrorFiles.ContainsKey("abc/def.cfg")); + + progress.Exception(config1, "An exception was thrown", e1); + Assert.Equal(1, progress.ExceptionCount); + Assert.Equal(1, progress.ErrorFiles["abc/def.cfg"]); + logger.Received().Exception("An exception was thrown", e1); + + progress.Exception(config2, "An exception was tossed", e2); + Assert.Equal(2, progress.ExceptionCount); + Assert.Equal(2, progress.ErrorFiles["abc/def.cfg"]); + logger.Received().Exception("An exception was tossed", e2); + } + } +} From ca12c392d34244cddc3eca746d66070f90ae9eee Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 7 Sep 2017 21:32:48 -0700 Subject: [PATCH 074/342] Replace DeepCopy with ConfigNode.CreateCopy It does 100% the same thing (and is recursive) --- ModuleManager/MMPatchLoader.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 196398cb..c3e8b574 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1112,7 +1112,7 @@ public IEnumerator ApplyPatch(string Stage) // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse. public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext context) { - ConfigNode newNode = DeepCopy(original.value); + ConfigNode newNode = original.value.CreateCopy(); NodeStack nodeStack = original.ReplaceValue(newNode); #region Values @@ -2390,19 +2390,6 @@ private static void ShallowCopy(ConfigNode from, ConfigNode to) to.nodes.Add(node); } - private static ConfigNode DeepCopy(ConfigNode from) - { - ConfigNode to = new ConfigNode(from.name); - foreach (ConfigNode.Value value in from.values) - to.AddValue(value.name, value.value); - foreach (ConfigNode node in from.nodes) - { - ConfigNode newNode = DeepCopy(node); - to.nodes.Add(newNode); - } - return to; - } - private string PrettyConfig(UrlDir.UrlConfig config) { StringBuilder sb = new StringBuilder(); From 2b824899ff271c0681f7d03f527ec5b549a2f98a Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 7 Sep 2017 21:36:58 -0700 Subject: [PATCH 075/342] Inline out variable declarations Yay C#7 --- ModuleManager/MMPatchLoader.cs | 47 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index c3e8b574..80e08a4c 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -184,8 +184,7 @@ private void PrePatchInit() modlist += "Non-DLL mods added (:FOR[xxx]):\n"; foreach (UrlDir.UrlConfig cfgmod in GameDatabase.Instance.root.AllConfigs) { - string name; - if (ParseCommand(cfgmod.type, out name) != Command.Insert) + if (ParseCommand(cfgmod.type, out string name) != Command.Insert) { progress.PatchAdded(); if (name.Contains(":FOR[")) @@ -969,8 +968,7 @@ public IEnumerator ApplyPatch(string Stage) try { string name = RemoveWS(mod.type); - string tmp; - Command cmd = ParseCommand(name, out tmp); + Command cmd = ParseCommand(name, out string tmp); if (cmd != Command.Insert) { @@ -1125,8 +1123,8 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon #if LOGSPAM vals += "\n " + modVal.name + "= " + modVal.value; #endif - string valName; - Command cmd = ParseCommand(modVal.name, out valName); + + Command cmd = ParseCommand(modVal.name, out string valName); if (cmd == Command.Special) { @@ -1149,8 +1147,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (assignMatch.Groups[2].Success) { - double os, s; - if (double.TryParse(modVal.value, out s) && double.TryParse(val.value, out os)) + if (double.TryParse(modVal.value, out double s) && double.TryParse(val.value, out double os)) { switch (assignMatch.Groups[2].Value[0]) { @@ -1295,9 +1292,19 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (varValue != null) { - ConfigNode.Value origVal; - string value = FindAndReplaceValue(mod, ref valName, varValue, newNode, op, index, - out origVal, context, match.Groups[3].Success, position, isPosStar, seperator); + string value = FindAndReplaceValue( + mod, + ref valName, + varValue, newNode, + op, + index, + out ConfigNode.Value origVal, + context, + match.Groups[3].Success, + position, + isPosStar, + seperator + ); if (value != null) { @@ -1417,16 +1424,14 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon } string subName = subMod.name; - string tmp; - Command command = ParseCommand(subName, out tmp); + Command command = ParseCommand(subName, out string tmp); if (command == Command.Insert) { ConfigNode newSubMod = new ConfigNode(subMod.name); newSubMod = ModifyNode(nodeStack.Push(newSubMod), subMod, context); subName = newSubMod.name; - int index; - if (subName.Contains(",") && int.TryParse(subName.Split(',')[1], out index)) + if (subName.Contains(",") && int.TryParse(subName.Split(',')[1], out int index)) { // In this case insert the node at position index (with the same node names) newSubMod.name = subName.Split(',')[0]; @@ -1461,8 +1466,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon ConfigNode newSubMod = new ConfigNode(toPaste.name); newSubMod = ModifyNode(nodeStack.Push(newSubMod), toPaste, context); - int index; - if (subName.LastIndexOf(",") > 0 && int.TryParse(subName.Substring(subName.LastIndexOf(",") + 1), out index)) + if (subName.LastIndexOf(",") > 0 && int.TryParse(subName.Substring(subName.LastIndexOf(",") + 1), out int index)) { // In this case insert the node at position index InsertNode(newNode, newSubMod, index); @@ -1917,8 +1921,7 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod if (match.Groups[3].Success) { ConfigNode.Value newVal = new ConfigNode.Value(cVal.name, cVal.value); - int splitIdx = 0; - int.TryParse(match.Groups[3].Value, out splitIdx); + int.TryParse(match.Groups[3].Value, out int splitIdx); char sep = ','; if (match.Groups[4].Success) @@ -1998,7 +2001,6 @@ private static string FindAndReplaceValue( oValue = strArray[posIndex]; if (op != ' ') { - double s, os; if (op == '^') { try @@ -2024,7 +2026,7 @@ private static string FindAndReplaceValue( return null; } } - else if (double.TryParse(value, out s) && double.TryParse(oValue, out os)) + else if (double.TryParse(value, out double s) && double.TryParse(oValue, out double os)) { switch (op) { @@ -2316,8 +2318,7 @@ public static bool WildcardMatchValues(ConfigNode node, string type, string valu if (!compare && WildcardMatch(values[i], value)) return true; - double val2; - if (compare && Double.TryParse(values[i], out val2) + if (compare && Double.TryParse(values[i], out double val2) && ((value[0] == '<' && val2 < val) || (value[0] == '>' && val2 > val))) { return true; From 47ce0154abaa501cb0cbcd10a709b9bd11bd370f Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 7 Sep 2017 21:40:26 -0700 Subject: [PATCH 076/342] Obey naming conventions --- ModuleManager/CustomConfigsManager.cs | 8 ++++---- ModuleManager/MMPatchLoader.cs | 11 +++++------ ModuleManager/ModuleManager.cs | 20 ++++++++++---------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/ModuleManager/CustomConfigsManager.cs b/ModuleManager/CustomConfigsManager.cs index 2ceba915..d88f4ea9 100644 --- a/ModuleManager/CustomConfigsManager.cs +++ b/ModuleManager/CustomConfigsManager.cs @@ -11,22 +11,22 @@ internal void Start() { if (HighLogic.CurrentGame.Parameters.Career.TechTreeUrl != MMPatchLoader.techTreeFile && File.Exists(MMPatchLoader.techTreePath)) { - log("Setting modded tech tree as the active one"); + Log("Setting modded tech tree as the active one"); HighLogic.CurrentGame.Parameters.Career.TechTreeUrl = MMPatchLoader.techTreeFile; } if (PhysicsGlobals.PhysicsDatabaseFilename != MMPatchLoader.physicsFile && File.Exists(MMPatchLoader.physicsPath)) { - log("Setting modded physics as the active one"); + Log("Setting modded physics as the active one"); PhysicsGlobals.PhysicsDatabaseFilename = MMPatchLoader.physicsFile; if (!PhysicsGlobals.Instance.LoadDatabase()) - log("Something went wrong while setting the active physics config."); + Log("Something went wrong while setting the active physics config."); } } - public static void log(String s) + public static void Log(String s) { print("[CustomConfigsManager] " + s); } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 80e08a4c..9fe38756 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -132,7 +132,7 @@ public void Update() StatusUpdate(); } - public static void addPostPatchCallback(ModuleManagerPostPatchCallback callback) + public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) { if (!postPatchCallbacks.Contains(callback)) postPatchCallbacks.Add(callback); @@ -584,7 +584,7 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) for (int i = 0; i < files.Length; i++) { - ConfigNode fileNode = getFileNode(shaConfigNode, files[i].url); + ConfigNode fileNode = GetFileNode(shaConfigNode, files[i].url); string fileSha = fileNode?.GetValue("SHA"); if (fileNode == null) @@ -598,7 +598,7 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) } for (int i = 0; i < files.Length; i++) { - ConfigNode fileNode = getFileNode(shaConfigNode, files[i].url); + ConfigNode fileNode = GetFileNode(shaConfigNode, files[i].url); if (fileNode == null) { @@ -617,7 +617,7 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) return noChange; } - private ConfigNode getFileNode(ConfigNode shaConfigNode, string filename) + private ConfigNode GetFileNode(ConfigNode shaConfigNode, string filename) { for (int i = 0; i < shaConfigNode.nodes.Count; i++) { @@ -2333,8 +2333,7 @@ public static bool WildcardMatch(string s, string wildcard) return true; string pattern = "^" + Regex.Escape(wildcard).Replace(@"\*", ".*").Replace(@"\?", ".") + "$"; - Regex regex; - if (!regexCache.TryGetValue(pattern, out regex)) + if (!regexCache.TryGetValue(pattern, out Regex regex)) { regex = new Regex(pattern); regexCache.Add(pattern, regex); diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 9bf3073f..856d4af2 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -49,7 +49,7 @@ internal void OnRnDCenterDeSpawn() inRnDCenter = false; } - public static void log(String s) + public static void Log(String s) { print("[ModuleManager] " + s); } @@ -67,7 +67,7 @@ internal void Awake() if (loadedInScene || !ElectionAndCheck()) { Assembly currentAssembly = Assembly.GetExecutingAssembly(); - log("Multiple copies of current version. Using the first copy. Version: " + + Log("Multiple copies of current version. Using the first copy. Version: " + currentAssembly.GetName().Version); Destroy(gameObject); return; @@ -85,7 +85,7 @@ internal void Awake() LoadingScreen screen = FindObjectOfType(); if (screen == null) { - log("Can't find LoadingScreen type. Aborting ModuleManager execution"); + Log("Can't find LoadingScreen type. Aborting ModuleManager execution"); return; } List list = LoadingScreen.Instance.loaders; @@ -100,7 +100,7 @@ internal void Awake() GameObject aGameObject = new GameObject("ModuleManager"); MMPatchLoader loader = aGameObject.AddComponent(); - log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); + Log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); list.Insert(gameDatabaseIndex + 1, loader); @@ -218,7 +218,7 @@ internal void Update() if (totalTime.IsRunning && HighLogic.LoadedScene == GameScenes.MAINMENU) { totalTime.Stop(); - log("Total loading Time = " + ((float)totalTime.ElapsedMilliseconds / 1000).ToString("F3") + "s"); + Log("Total loading Time = " + ((float)totalTime.ElapsedMilliseconds / 1000).ToString("F3") + "s"); Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND; } @@ -297,7 +297,7 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) while (!MMPatchLoader.Instance.IsReady()) yield return null; - log("DB Reload OK with patchCount=" + MMPatchLoader.Instance.progress.PatchedNodeCount + " errorCount=" + + Log("DB Reload OK with patchCount=" + MMPatchLoader.Instance.progress.PatchedNodeCount + " errorCount=" + MMPatchLoader.Instance.progress.ErrorCount + " needsUnsatisfiedCount=" + MMPatchLoader.Instance.progress.NeedsUnsatisfiedCount + " exceptionCount=" + MMPatchLoader.Instance.progress.ExceptionCount); @@ -350,7 +350,7 @@ private static void OutputAllConfigs() } catch (Exception e) { - log("Exception while trying to write the file " + filePath + "\n" + e); + Log("Exception while trying to write the file " + filePath + "\n" + e); } } } @@ -384,7 +384,7 @@ public bool ElectionAndCheck() "You have old versions of Module Manager (older than 1.5) or MMSarbianExt.\nYou will need to remove them for Module Manager and the mods using it to work\nExit KSP and delete those files :\n" + String.Join("\n", badPaths.ToArray()); PopupDialog.SpawnPopupDialog(new Vector2(0f, 1f), new Vector2(0f, 1f), "ModuleManagerOldVersions", "Old versions of Module Manager", status, "OK", false, UISkinManager.defaultSkin); - log("Old version of Module Manager present. Stopping"); + Log("Old version of Module Manager present. Stopping"); return false; } @@ -404,7 +404,7 @@ orderby ass.GetName().Version descending, a.path ascending if (eligible.First().assembly != currentAssembly) { //loaded = true; - log("version " + currentAssembly.GetName().Version + " at " + currentAssembly.Location + + Log("version " + currentAssembly.GetName().Version + " at " + currentAssembly.Location + " lost the election"); Destroy(gameObject); return false; @@ -417,7 +417,7 @@ orderby ass.GetName().Version descending, a.path ascending } if (candidates.Length > 0) { - log("version " + currentAssembly.GetName().Version + " at " + currentAssembly.Location + + Log("version " + currentAssembly.GetName().Version + " at " + currentAssembly.Location + " won the election against\n" + candidates); } From a9a990d7f119d0d2d30f50f85a10d8398d27c2b1 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 20:03:40 -0700 Subject: [PATCH 077/342] Pull Command and ParseCommand out of MMPatchLoader Would be nice if enums allowed static methods --- ModuleManager/Command.cs | 28 +++++++ ModuleManager/CommandParser.cs | 62 ++++++++++++++ ModuleManager/MMPatchLoader.cs | 87 ++------------------ ModuleManager/ModuleManager.csproj | 2 + ModuleManagerTests/CommandParserTest.cs | 86 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 6 files changed, 184 insertions(+), 82 deletions(-) create mode 100644 ModuleManager/Command.cs create mode 100644 ModuleManager/CommandParser.cs create mode 100644 ModuleManagerTests/CommandParserTest.cs diff --git a/ModuleManager/Command.cs b/ModuleManager/Command.cs new file mode 100644 index 00000000..b4c6ffc8 --- /dev/null +++ b/ModuleManager/Command.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ModuleManager +{ + public enum Command + { + Insert, + + Delete, + + Edit, + + Replace, + + Copy, + + Rename, + + Paste, + + Special, + + Create + } +} diff --git a/ModuleManager/CommandParser.cs b/ModuleManager/CommandParser.cs new file mode 100644 index 00000000..fd587441 --- /dev/null +++ b/ModuleManager/CommandParser.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ModuleManager +{ + public static class CommandParser + { + public static Command Parse(string name, out string valueName) + { + if (name.Length == 0) + { + valueName = string.Empty; + return Command.Insert; + } + Command ret; + switch (name[0]) + { + case '@': + ret = Command.Edit; + break; + + case '%': + ret = Command.Replace; + break; + + case '-': + case '!': + ret = Command.Delete; + break; + + case '+': + case '$': + ret = Command.Copy; + break; + + case '|': + ret = Command.Rename; + break; + + case '#': + ret = Command.Paste; + break; + + case '*': + ret = Command.Special; + break; + + case '&': + ret = Command.Create; + break; + + default: + valueName = name; + return Command.Insert; + } + valueName = name.Substring(1); + return ret; + } + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 9fe38756..6c18604f 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -184,7 +184,7 @@ private void PrePatchInit() modlist += "Non-DLL mods added (:FOR[xxx]):\n"; foreach (UrlDir.UrlConfig cfgmod in GameDatabase.Instance.root.AllConfigs) { - if (ParseCommand(cfgmod.type, out string name) != Command.Insert) + if (CommandParser.Parse(cfgmod.type, out string name) != Command.Insert) { progress.PatchAdded(); if (name.Contains(":FOR[")) @@ -940,7 +940,7 @@ private void PurgeUnused() { string name = RemoveWS(mod.type); - if (ParseCommand(name, out name) != Command.Insert) + if (CommandParser.Parse(name, out name) != Command.Insert) mod.parent.configs.Remove(mod); } } @@ -968,7 +968,7 @@ public IEnumerator ApplyPatch(string Stage) try { string name = RemoveWS(mod.type); - Command cmd = ParseCommand(name, out string tmp); + Command cmd = CommandParser.Parse(name, out string tmp); if (cmd != Command.Insert) { @@ -1124,7 +1124,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon vals += "\n " + modVal.name + "= " + modVal.value; #endif - Command cmd = ParseCommand(modVal.name, out string valName); + Command cmd = CommandParser.Parse(modVal.name, out string valName); if (cmd == Command.Special) { @@ -1424,7 +1424,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon } string subName = subMod.name; - Command command = ParseCommand(subName, out string tmp); + Command command = CommandParser.Parse(subName, out string tmp); if (command == Command.Insert) { @@ -2068,83 +2068,6 @@ private static string FindAndReplaceValue( #endregion Applying Patches - #region Command Parsing - - private enum Command - { - Insert, - - Delete, - - Edit, - - Replace, - - Copy, - - Rename, - - Paste, - - Special, - - Create - } - - private static Command ParseCommand(string name, out string valueName) - { - if (name.Length == 0) - { - valueName = string.Empty; - return Command.Insert; - } - Command ret; - switch (name[0]) - { - case '@': - ret = Command.Edit; - break; - - case '%': - ret = Command.Replace; - break; - - case '-': - case '!': - ret = Command.Delete; - break; - - case '+': - case '$': - ret = Command.Copy; - break; - - case '|': - ret = Command.Rename; - break; - - case '#': - ret = Command.Paste; - break; - - case '*': - ret = Command.Special; - break; - - case '&': - ret = Command.Create; - break; - - default: - valueName = name; - return Command.Insert; - } - valueName = name.Substring(1); - return ret; - } - - #endregion Command Parsing - #region Sanity checking & Utility functions public static bool IsBracketBalanced(string str) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 92a34368..cae119e4 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -36,6 +36,8 @@ + + diff --git a/ModuleManagerTests/CommandParserTest.cs b/ModuleManagerTests/CommandParserTest.cs new file mode 100644 index 00000000..fd5d729e --- /dev/null +++ b/ModuleManagerTests/CommandParserTest.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class CommandParserTest + { + [Fact] + public void TestParse__Insert() + { + Assert.Equal(Command.Insert, CommandParser.Parse("PART", out string newName)); + Assert.Equal("PART", newName); + } + + [Fact] + public void TestParse__Delete() + { + Assert.Equal(Command.Delete, CommandParser.Parse("!PART", out string newName1)); + Assert.Equal("PART", newName1); + Assert.Equal(Command.Delete, CommandParser.Parse("-PART", out string newName2)); + Assert.Equal("PART", newName2); + } + + [Fact] + public void TestParse__Edit() + { + Assert.Equal(Command.Edit, CommandParser.Parse("@PART", out string newName)); + Assert.Equal("PART", newName); + } + + [Fact] + public void TestParse__Replace() + { + Assert.Equal(Command.Replace, CommandParser.Parse("%PART", out string newName)); + Assert.Equal("PART", newName); + } + + [Fact] + public void TestParse__Copy() + { + Assert.Equal(Command.Copy, CommandParser.Parse("+PART", out string newName1)); + Assert.Equal("PART", newName1); + Assert.Equal(Command.Copy, CommandParser.Parse("$PART", out string newName2)); + Assert.Equal("PART", newName2); + } + + [Fact] + public void TestParse__Rename() + { + Assert.Equal(Command.Rename, CommandParser.Parse("|PART", out string newName)); + Assert.Equal("PART", newName); ; + } + + [Fact] + public void TestParse__Paste() + { + Assert.Equal(Command.Paste, CommandParser.Parse("#PART", out string newName)); + Assert.Equal("PART", newName); + } + + [Fact] + public void TestParse__Special() + { + Assert.Equal(Command.Special, CommandParser.Parse("*PART", out string newName)); + Assert.Equal("PART", newName); + } + + [Fact] + public void TestParse__Special__Chained() + { + Assert.Equal(Command.Special, CommandParser.Parse("*@PART", out string newName)); + Assert.Equal("@PART", newName); + } + + [Fact] + public void TestParse__Create() + { + Assert.Equal(Command.Create, CommandParser.Parse("&PART", out string newName)); + Assert.Equal("PART", newName); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index df8d088e..ece63f63 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -50,6 +50,7 @@ + From b43f79b20c2759797aa1ad6323331d3e5e834b85 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 20:05:07 -0700 Subject: [PATCH 078/342] Extract ShallowCopy "this" is the node you're copying to so that the extension method is only modifying "its" node --- .../Extensions/ConfigNodeExtensions.cs | 19 ++++ ModuleManager/MMPatchLoader.cs | 13 +-- ModuleManager/ModuleManager.csproj | 1 + .../Extensions/ConfigNodeExtensionsTest.cs | 87 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 5 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 ModuleManager/Extensions/ConfigNodeExtensions.cs create mode 100644 ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs new file mode 100644 index 00000000..bf876692 --- /dev/null +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ModuleManager.Extensions +{ + public static class ConfigNodeExtensions + { + public static void ShallowCopyFrom(this ConfigNode toNode, ConfigNode fromeNode) + { + toNode.ClearData(); + foreach (ConfigNode.Value value in fromeNode.values) + toNode.values.Add(value); + foreach (ConfigNode node in fromeNode.nodes) + toNode.nodes.Add(node); + } + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 6c18604f..8c0953e8 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -802,7 +802,7 @@ private void CheckNeeds() } ConfigNode copy = new ConfigNode(type); - ShallowCopy(currentMod.config, copy); + copy.ShallowCopyFrom(currentMod.config); currentMod = new UrlDir.UrlConfig(currentMod.parent, copy); mod.parent.configs.Add(currentMod); } @@ -889,7 +889,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context) } if (needsCopy) - ShallowCopy(copy, original); + original.ShallowCopyFrom(copy); } /// @@ -2304,15 +2304,6 @@ private static void InsertValue(ConfigNode newNode, int index, string name, stri newNode.AddValue(name, value); } - private static void ShallowCopy(ConfigNode from, ConfigNode to) - { - to.ClearData(); - foreach (ConfigNode.Value value in from.values) - to.values.Add(value); - foreach (ConfigNode node in from.nodes) - to.nodes.Add(node); - } - private string PrettyConfig(UrlDir.UrlConfig config) { StringBuilder sb = new StringBuilder(); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index cae119e4..b6cc5543 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -38,6 +38,7 @@ + diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs new file mode 100644 index 00000000..d48c2b2f --- /dev/null +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; +using TestUtils; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class ConfigNodeExtensionsTest + { + public void TestShallowCopyFrom() + { + ConfigNode fromNode = new TestConfigNode("SOME_NODE") + { + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE_1") + { + { "mno", "pqr" }, + new TestConfigNode("INNER_INNER_NODE_1"), + }, + new TestConfigNode("INNER_NODE_2") + { + { "stu", "vwx" }, + new TestConfigNode("INNER_INNER_NODE_2"), + }, + }; + + ConfigNode.Value value1 = fromNode.values[0]; + ConfigNode.Value value2 = fromNode.values[1]; + + ConfigNode innerNode1 = fromNode.nodes[0]; + ConfigNode innerNode2 = fromNode.nodes[1]; + + ConfigNode toNode = new TestConfigNode("SOME_OTHER_NODE") + { + { "value", "will be removed" }, + new TestConfigNode("NODE_WILL_BE_REMOVED"), + }; + + toNode.ShallowCopyFrom(fromNode); + + Assert.Equal("SOME_NODE", fromNode.name); + Assert.Equal("SOME_OTHER_NODE", toNode.name); + + Assert.Equal(2, fromNode.values.Count); + Assert.Equal(2, toNode.values.Count); + + Assert.Same(value1, fromNode.values[0]); + Assert.Same(value1, toNode.values[0]); + Assert.Equal("abc", value1.name); + Assert.Equal("def", value1.value); + + Assert.Same(value2, fromNode.values[1]); + Assert.Same(value2, toNode.values[1]); + Assert.Equal("ghi", value2.name); + Assert.Equal("jkl", value2.value); + + Assert.Equal(2, fromNode.nodes.Count); + Assert.Equal(2, toNode.nodes.Count); + + Assert.Same(innerNode1, fromNode.nodes[0]); + Assert.Same(innerNode1, toNode.nodes[0]); + Assert.Equal("INNER_NODE_1", innerNode1.name); + Assert.Equal(1, innerNode1.values.Count); + Assert.Equal("mno", innerNode1.values[0].name); + Assert.Equal("pqr", innerNode1.values[0].value); + Assert.Equal(1, innerNode1.nodes.Count); + Assert.Equal("INNER_INNER_NODE_1", innerNode1.nodes[0].name); + Assert.Equal(0, innerNode1.nodes[0].values.Count); + Assert.Equal(0, innerNode1.nodes[0].nodes.Count); + + Assert.Same(innerNode2, fromNode.nodes[1]); + Assert.Same(innerNode2, toNode.nodes[1]); + Assert.Equal("INNER_NODE_2", innerNode2.name); + Assert.Equal(1, innerNode2.values.Count); + Assert.Equal("stu", innerNode2.values[0].name); + Assert.Equal("vwx", innerNode2.values[0].value); + Assert.Equal(1, innerNode2.nodes.Count); + Assert.Equal("INNER_INNER_NODE_2", innerNode2.nodes[0].name); + Assert.Equal(0, innerNode2.nodes[0].values.Count); + Assert.Equal(0, innerNode2.nodes[0].nodes.Count); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index ece63f63..24440b4d 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -52,6 +52,7 @@ + From 534eee2d6cc93d7e3bf6a8899e201f674c957cb2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 20:06:14 -0700 Subject: [PATCH 079/342] Don't create duplicates in UrlBuilder --- TestUtils/URLBuilder.cs | 21 ++++++- TestUtilsTests/UrlBuilderTest.cs | 102 +++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/TestUtils/URLBuilder.cs b/TestUtils/URLBuilder.cs index 5253d158..7ad78fd6 100644 --- a/TestUtils/URLBuilder.cs +++ b/TestUtils/URLBuilder.cs @@ -41,7 +41,15 @@ public static UrlDir CreateRoot() public static UrlDir CreateDir(string url, UrlDir parent = null) { - if (parent == null) parent = CreateRoot(); + if (parent == null) + { + parent = CreateRoot(); + } + else + { + UrlDir existingDir = parent.GetDirectory(url); + if (existingDir != null) return existingDir; + } UrlDir current = parent; @@ -74,10 +82,17 @@ public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) parent = CreateRoot(); } + string nameWithoutExtension = Path.GetFileNameWithoutExtension(name); + string extension = Path.GetExtension(name); + if (!string.IsNullOrEmpty(extension)) extension = extension.Substring(1); + + UrlDir.UrlFile existingFile = parent.files.FirstOrDefault(f => f.name == nameWithoutExtension && f.fileExtension == extension); + if (existingFile != null) return existingFile; + bool cfg = false; string newName = name; - if (Path.GetExtension(name) == ".cfg") + if (extension == "cfg") { cfg = true; newName = name + ".not_cfg"; @@ -87,7 +102,7 @@ public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) if (cfg) { - UrlFile__field__name.SetValue(file, Path.GetFileNameWithoutExtension(name)); + UrlFile__field__name.SetValue(file, nameWithoutExtension); UrlFile__field__fileExtension.SetValue(file, "cfg"); UrlFile__field__fileType.SetValue(file, UrlDir.FileType.Config); } diff --git a/TestUtilsTests/UrlBuilderTest.cs b/TestUtilsTests/UrlBuilderTest.cs index 06190441..c0bcb9c2 100644 --- a/TestUtilsTests/UrlBuilderTest.cs +++ b/TestUtilsTests/UrlBuilderTest.cs @@ -82,6 +82,8 @@ public void TestCreateDir__Url() Assert.Same(root, root.root); Assert.Same(root, parent.root); Assert.Same(root, dir.root); + + Assert.Contains(dir, root.AllDirectories); } [Fact] @@ -105,6 +107,32 @@ public void TestCreateDir__Url__Parent() Assert.Same(root, dir.root); Assert.Same(root, parent2.root); + + Assert.Contains(dir, root.AllDirectories); + Assert.Contains(dir, parent1.AllDirectories); + } + + [Fact] + public void TestCreateDir__Url__AlreadyExists() + { + UrlDir root = UrlBuilder.CreateRoot(); + + UrlDir dir1 = UrlBuilder.CreateDir("abc/def", root); + UrlDir dir2 = UrlBuilder.CreateDir("abc/def", root); + + Assert.Same(dir1, dir2); + + Assert.Equal("def", dir1.name); + + UrlDir parent = dir1.parent; + + Assert.NotNull(parent); + Assert.Equal("abc", parent.name); + Assert.Contains(dir1, parent.children); + + Assert.Same(root, dir1.root); + Assert.Same(root, parent.root); + Assert.Contains(dir1, root.AllDirectories); } [Fact] @@ -196,6 +224,31 @@ public void TestCreateFile__Url() Assert.Contains(file, parent2.AllFiles); } + [Fact] + public void TestCreateFile__Url__AlreadyExists() + { + UrlDir root = UrlBuilder.CreateRoot(); + UrlDir.UrlFile file1 = UrlBuilder.CreateFile("abc/def.txt", root); + UrlDir.UrlFile file2 = UrlBuilder.CreateFile("abc/def.txt", root); + + Assert.Same(file1, file2); + + Assert.Equal("def", file1.name); + Assert.Equal("txt", file1.fileExtension); + Assert.Equal(UrlDir.FileType.Unknown, file1.fileType); + Assert.Equal("abc/def", file1.url); + + UrlDir parent1 = file1.parent; + Assert.NotNull(parent1); + Assert.Equal("abc", parent1.name); + Assert.Contains(file1, parent1.files); + + Assert.Same(root, file1.root); + Assert.Same(root, parent1.root); + + Assert.Contains(file1, root.AllFiles); + } + [Fact] public void TestCreateConfig() { @@ -255,5 +308,54 @@ public void TestCreateConfig__Url() Assert.Contains(config, root.AllConfigs); Assert.Contains(config, root.GetConfigs("SOME_NODE")); } + + [Fact] + public void TestCreateConfig__Url__FileAlreadyExists() + { + UrlDir root = UrlBuilder.CreateRoot(); + + ConfigNode node1 = new TestConfigNode("SOME_NODE") + { + { "name", "blah" }, + { "foo", "bar" }, + }; + + ConfigNode node2 = new TestConfigNode("SOME_OTHER_NODE") + { + { "name", "bleh" }, + { "jazz", "hands" }, + }; + + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", node1, root); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("abc/def", node2, root); + + UrlDir.UrlFile file = config1.parent; + Assert.NotNull(file); + + Assert.Same(file, config2.parent); + + Assert.Equal("def", file.name); + Assert.Equal("cfg", file.fileExtension); + Assert.Equal(UrlDir.FileType.Config, file.fileType); + Assert.Contains(config1, file.configs); + Assert.Contains(config2, file.configs); + + UrlDir parent = file.parent; + Assert.NotNull(parent); + Assert.Equal("abc", parent.name); + Assert.Contains(file, parent.files); + + Assert.Contains(parent, root.children); + + Assert.Same(root, file.root); + Assert.Same(root, parent.root); + Assert.Same(root, root.root); + + Assert.Contains(config1, root.AllConfigs); + Assert.Contains(config1, root.GetConfigs("SOME_NODE")); + + Assert.Contains(config2, root.AllConfigs); + Assert.Contains(config2, root.GetConfigs("SOME_OTHER_NODE")); + } } } From 24f21435b3efc6e0064de654a679ec8d5afba2d3 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 21:10:48 -0700 Subject: [PATCH 080/342] Add ArrayEnumerator Enumerates arrays in a garbage-free way --- ModuleManager/Collections/ArrayEnumerator.cs | 34 +++++++++++ ModuleManager/ModuleManager.csproj | 1 + .../Collections/ArrayEnumeratorTest.cs | 56 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 4 files changed, 92 insertions(+) create mode 100644 ModuleManager/Collections/ArrayEnumerator.cs create mode 100644 ModuleManagerTests/Collections/ArrayEnumeratorTest.cs diff --git a/ModuleManager/Collections/ArrayEnumerator.cs b/ModuleManager/Collections/ArrayEnumerator.cs new file mode 100644 index 00000000..d002b2ad --- /dev/null +++ b/ModuleManager/Collections/ArrayEnumerator.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace ModuleManager.Collections +{ + public struct ArrayEnumerator : IEnumerator + { + private readonly T[] array; + private int index; + + public ArrayEnumerator(T[] array) + { + this.array = array; + index = -1; + } + + public T Current => array[index]; + object IEnumerator.Current => Current; + + public void Dispose() { } + + public bool MoveNext() + { + index++; + return index < array.Length; + } + + public void Reset() + { + index = -1; + } + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index b6cc5543..58a96cfd 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -35,6 +35,7 @@ + diff --git a/ModuleManagerTests/Collections/ArrayEnumeratorTest.cs b/ModuleManagerTests/Collections/ArrayEnumeratorTest.cs new file mode 100644 index 00000000..34eac1ed --- /dev/null +++ b/ModuleManagerTests/Collections/ArrayEnumeratorTest.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Xunit; +using ModuleManager.Collections; + +namespace ModuleManagerTests.Collections +{ + public class ArrayEnumeratorTest + { + [Fact] + public void TestArrayEnumerator() + { + string[] arr = { "abc", "def", "ghi" }; + + IEnumerator enumerator = new ArrayEnumerator(arr); + + Assert.True(enumerator.MoveNext()); + Assert.Equal("abc", enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal("def", enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal("ghi", enumerator.Current); + Assert.False(enumerator.MoveNext()); + } + + [Fact] + public void TestArrayEnumerator__Reset() + { + string[] arr = { "abc", "def" }; + + IEnumerator enumerator = new ArrayEnumerator(arr); + + Assert.True(enumerator.MoveNext()); + Assert.Equal("abc", enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal("def", enumerator.Current); + Assert.False(enumerator.MoveNext()); + + enumerator.Reset(); + + Assert.True(enumerator.MoveNext()); + Assert.Equal("abc", enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal("def", enumerator.Current); + Assert.False(enumerator.MoveNext()); + } + + [Fact] + public void TestArrayEnumerator__Empty() + { + string[] arr = new string[0]; + IEnumerator enumerator = new ArrayEnumerator(arr); + Assert.False(enumerator.MoveNext()); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 24440b4d..c86f9a15 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -49,6 +49,7 @@ + From 7138bbca48c127dda038d199b1518a0dfc84a0a5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 21:59:01 -0700 Subject: [PATCH 081/342] PatchList list of patches, 'nuff said --- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/PatchList.cs | 66 +++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PatchListTest.cs | 74 ++++++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 ModuleManager/PatchList.cs create mode 100644 ModuleManagerTests/PatchListTest.cs diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 58a96cfd..812217d7 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -47,6 +47,7 @@ + diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs new file mode 100644 index 00000000..a120f633 --- /dev/null +++ b/ModuleManager/PatchList.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using ModuleManager.Collections; + +namespace ModuleManager +{ + public class PatchList + { + public class ModPass + { + public readonly List beforePatches = new List(0); + public readonly List forPatches = new List(0); + public readonly List afterPatches = new List(0); + + public readonly string name; + + public ModPass(string name) + { + this.name = name; + } + } + + public class ModPassCollection : IEnumerable + { + private readonly ModPass[] passesArray; + private readonly Dictionary passesDict; + + public ModPassCollection(IEnumerable modList) + { + int count = modList.Count(); + passesArray = new ModPass[count]; + passesDict = new Dictionary(count); + + int i = 0; + foreach (string mod in modList) + { + ModPass pass = new ModPass(mod); + passesArray[i] = pass; + passesDict.Add(mod, pass); + i++; + } + } + + public ModPass this[string name] => passesDict[name]; + + public bool HasMod(string name) => passesDict.ContainsKey(name); + + public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(passesArray); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public readonly List firstPatches = new List(); + public readonly List legacyPatches = new List(); + public readonly List finalPatches = new List(); + + public readonly ModPassCollection modPasses; + + public PatchList(IEnumerable modList) + { + modPasses = new ModPassCollection(modList); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index c86f9a15..b8f47f39 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -49,6 +49,7 @@ + diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs new file mode 100644 index 00000000..83061aaf --- /dev/null +++ b/ModuleManagerTests/PatchListTest.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using Xunit; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class PatchListTest + { + [Fact] + public void TestConstructor() + { + PatchList list = new PatchList(new string[0]); + + Assert.NotNull(list.firstPatches); + Assert.NotNull(list.legacyPatches); + Assert.NotNull(list.finalPatches); + Assert.NotNull(list.modPasses); + } + + [Fact] + public void TestModPasses__HasMod() + { + PatchList list = new PatchList(new[] { "mod1", "mod2" }); + + PatchList.ModPassCollection collection = list.modPasses; + + Assert.True(collection.HasMod("mod1")); + Assert.True(collection.HasMod("mod2")); + Assert.False(collection.HasMod("mod3")); + } + + [Fact] + public void TestModPasses__Accessor() + { + PatchList list = new PatchList(new[] { "mod1", "mod2" }); + + PatchList.ModPass pass1 = list.modPasses["mod1"]; + Assert.NotNull(pass1); + Assert.Equal("mod1", pass1.name); + Assert.NotNull(pass1.beforePatches); + Assert.Equal(0, pass1.beforePatches.Capacity); + Assert.NotNull(pass1.forPatches); + Assert.Equal(0, pass1.forPatches.Capacity); + Assert.NotNull(pass1.afterPatches); + Assert.Equal(0, pass1.afterPatches.Capacity); + + PatchList.ModPass pass2 = list.modPasses["mod2"]; + Assert.NotNull(pass2); + Assert.Equal("mod2", pass2.name); + Assert.NotNull(pass2.beforePatches); + Assert.Equal(0, pass2.beforePatches.Capacity); + Assert.NotNull(pass2.forPatches); + Assert.Equal(0, pass2.forPatches.Capacity); + Assert.NotNull(pass2.afterPatches); + Assert.Equal(0, pass2.afterPatches.Capacity); + + Assert.Throws(delegate + { + PatchList.ModPass mod3 = list.modPasses["mod3"]; + }); + } + + [Fact] + public void TestModPasses__Enumeration() + { + PatchList list = new PatchList(new[] { "mod1", "mod2" }); + + PatchList.ModPass[] passes = new PatchList.ModPass[] { list.modPasses["mod1"], list.modPasses["mod2"] }; + + Assert.Equal(passes, list.modPasses); + } + } +} From 9a55a65987f83afbc0d2df67b0c1e4c8d80a47e6 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 22:02:00 -0700 Subject: [PATCH 082/342] Add PatchExtractor Extracts patches from the game database and sorts them --- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/PatchExtractor.cs | 153 +++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PatchExtractorTest.cs | 315 +++++++++++++++++++ 4 files changed, 470 insertions(+) create mode 100644 ModuleManager/PatchExtractor.cs create mode 100644 ModuleManagerTests/PatchExtractorTest.cs diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 812217d7..dbe672d1 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -47,6 +47,7 @@ + diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs new file mode 100644 index 00000000..3f4be072 --- /dev/null +++ b/ModuleManager/PatchExtractor.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using ModuleManager.Extensions; + +namespace ModuleManager +{ + public static class PatchExtractor + { + private static readonly Regex firstRegex = new Regex(@":FIRST", RegexOptions.IgnoreCase); + private static readonly Regex finalRegex = new Regex(@":FINAL", RegexOptions.IgnoreCase); + private static readonly Regex beforeRegex = new Regex(@":BEFORE\[([^\[\]]+)\]", RegexOptions.IgnoreCase); + private static readonly Regex forRegex = new Regex(@":FOR\[([^\[\]]+)\]", RegexOptions.IgnoreCase); + private static readonly Regex afterRegex = new Regex(@":AFTER\[([^\[\]]+)\]", RegexOptions.IgnoreCase); + + public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable modList, IPatchProgress progress) + { + PatchList list = new PatchList(modList); + + foreach (UrlDir.UrlConfig url in databaseRoot.AllConfigs.ToArray()) + { + try + { + Command command = CommandParser.Parse(url.type, out _);; + + Match firstMatch = firstRegex.Match(url.type); + Match finalMatch = finalRegex.Match(url.type); + Match beforeMatch = beforeRegex.Match(url.type); + Match forMatch = forRegex.Match(url.type); + Match afterMatch = afterRegex.Match(url.type); + + int matchCount = 0; + + if (firstMatch.Success) matchCount++; + if (finalMatch.Success) matchCount++; + if (beforeMatch.Success) matchCount++; + if (forMatch.Success) matchCount++; + if (afterMatch.Success) matchCount++; + + if (firstMatch.NextMatch().Success) matchCount++; + if (finalMatch.NextMatch().Success) matchCount++; + if (beforeMatch.NextMatch().Success) matchCount++; + if (forMatch.NextMatch().Success) matchCount++; + if (afterMatch.NextMatch().Success) matchCount++; + + bool error = false; + + if (command == Command.Insert && matchCount > 0) + { + progress.Error(url, $"Error - pass specifier detected on an insert node (not a patch): {url.parent.url}/{url.type}"); + error = true; + } + if (matchCount > 1) + { + progress.Error(url, $"Error - more than one pass specifier on a node: {url.parent.url}/{url.type}"); + error = true; + } + if (error) + { + url.parent.configs.Remove(url); + continue; + } + + if (command == Command.Insert) continue; + + url.parent.configs.Remove(url); + + Match theMatch = null; + List thePass = null; + bool modNotFound = false; + + if (firstMatch.Success) + { + theMatch = firstMatch; + thePass = list.firstPatches; + } + else if (finalMatch.Success) + { + theMatch = finalMatch; + thePass = list.finalPatches; + } + else if (beforeMatch.Success) + { + if (CheckMod(beforeMatch, list.modPasses, out string theMod)) + { + theMatch = beforeMatch; + thePass = list.modPasses[theMod].beforePatches; + } + else + { + modNotFound = true; + } + } + else if (forMatch.Success) + { + if (CheckMod(forMatch, list.modPasses, out string theMod)) + { + theMatch = forMatch; + thePass = list.modPasses[theMod].forPatches; + } + else + { + modNotFound = true; + } + } + else if (afterMatch.Success) + { + if (CheckMod(afterMatch, list.modPasses, out string theMod)) + { + theMatch = afterMatch; + thePass = list.modPasses[theMod].afterPatches; + } + else + { + modNotFound = true; + } + } + else + { + thePass = list.legacyPatches; + } + + if (modNotFound) continue; + + UrlDir.UrlConfig newUrl = url; + if (theMatch != null) + { + string newName = url.type.Remove(theMatch.Index, theMatch.Length); + ConfigNode newNode = new ConfigNode(newName) { id = url.config.id }; + newNode.ShallowCopyFrom(url.config); + newUrl = new UrlDir.UrlConfig(url.parent, newNode); + } + + thePass.Add(newUrl); + } + catch(Exception e) + { + progress.Exception(url, $"Exception while parsing pass for config: {url.parent.url}/{url.type}", e); + } + } + + return list; + } + + private static bool CheckMod(Match match, PatchList.ModPassCollection modPasses, out string theMod) + { + theMod = match.Groups[1].Value.Trim().ToLower(); + return modPasses.HasMod(theMod); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index b8f47f39..6d67d5de 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -57,6 +57,7 @@ + diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs new file mode 100644 index 00000000..35453e9a --- /dev/null +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -0,0 +1,315 @@ +using System; +using System.Collections.Generic; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class PatchExtractorTest + { + private UrlDir root; + private UrlDir.UrlFile file; + + private IPatchProgress progress; + + public PatchExtractorTest() + { + root = UrlBuilder.CreateRoot(); + file = UrlBuilder.CreateFile("abc/def.cfg", root); + + progress = Substitute.For(); + } + + [Fact] + public void TestSortAndExtractPatches() + { + UrlDir.UrlConfig[] insertConfigs = + { + CreateConfig("NODE"), + CreateConfig("NADE"), + }; + + UrlDir.UrlConfig[] legacyConfigs = + { + CreateConfig("@NODE"), + CreateConfig("@NADE[foo]:HAS[#bar]"), + }; + + UrlDir.UrlConfig[] firstConfigs = + { + CreateConfig("@NODE:FIRST"), + CreateConfig("@NODE[foo]:HAS[#bar]:FIRST"), + CreateConfig("@NADE:First"), + CreateConfig("@NADE:first"), + }; + + UrlDir.UrlConfig[] finalConfigs = + { + CreateConfig("@NODE:FINAL"), + CreateConfig("@NODE[foo]:HAS[#bar]:FINAL"), + CreateConfig("@NADE:Final"), + CreateConfig("@NADE:final"), + }; + + UrlDir.UrlConfig[] beforeMod1Configs = + { + CreateConfig("@NODE:BEFORE[mod1]"), + CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod1]"), + CreateConfig("@NADE:before[mod1]"), + CreateConfig("@NADE:BEFORE[MOD1]"), + }; + + UrlDir.UrlConfig[] forMod1Configs = + { + CreateConfig("@NODE:FOR[mod1]"), + CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod1]"), + CreateConfig("@NADE:for[mod1]"), + CreateConfig("@NADE:FOR[MOD1]"), + }; + + UrlDir.UrlConfig[] afterMod1Configs = + { + CreateConfig("@NODE:AFTER[mod1]"), + CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod1]"), + CreateConfig("@NADE:after[mod1]"), + CreateConfig("@NADE:AFTER[MOD1]"), + }; + + UrlDir.UrlConfig[] beforeMod2Configs = + { + CreateConfig("@NODE:BEFORE[mod2]"), + CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod2]"), + CreateConfig("@NADE:before[mod2]"), + CreateConfig("@NADE:BEFORE[MOD2]"), + }; + + UrlDir.UrlConfig[] forMod2Configs = + { + CreateConfig("@NODE:FOR[mod2]"), + CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod2]"), + CreateConfig("@NADE:for[mod2]"), + CreateConfig("@NADE:FOR[MOD2]"), + }; + + UrlDir.UrlConfig[] afterMod2Configs = + { + CreateConfig("@NODE:AFTER[mod2]"), + CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod2]"), + CreateConfig("@NADE:after[mod2]"), + CreateConfig("@NADE:AFTER[MOD2]"), + }; + + UrlDir.UrlConfig[] beforeMod3Configs = + { + CreateConfig("@NODE:BEFORE[mod3]"), + CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod3]"), + CreateConfig("@NADE:before[mod3]"), + CreateConfig("@NADE:BEFORE[MOD3]"), + }; + + UrlDir.UrlConfig[] forMod3Configs = + { + CreateConfig("@NODE:FOR[mod3]"), + CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod3]"), + CreateConfig("@NADE:for[mod3]"), + CreateConfig("@NADE:FOR[MOD3]"), + }; + + UrlDir.UrlConfig[] afterMod3Configs = + { + CreateConfig("@NODE:AFTER[mod3]"), + CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod3]"), + CreateConfig("@NADE:after[mod3]"), + CreateConfig("@NADE:AFTER[MOD3]"), + }; + + string[] modList = { "mod1", "mod2" }; + PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + Assert.True(list.modPasses.HasMod("mod1")); + Assert.True(list.modPasses.HasMod("mod2")); + Assert.False(list.modPasses.HasMod("mod3")); + + Assert.Equal(insertConfigs, root.AllConfigs); + + Assert.Equal(legacyConfigs, list.legacyPatches); + + List currentPatches; + + currentPatches = list.firstPatches; + Assert.Equal(firstConfigs.Length, currentPatches.Count); + AssertUrlCorrect("@NODE", firstConfigs[0], currentPatches[0]); + AssertUrlCorrect("@NODE[foo]:HAS[#bar]", firstConfigs[1], currentPatches[1]); + AssertUrlCorrect("@NADE", firstConfigs[2], currentPatches[2]); + AssertUrlCorrect("@NADE", firstConfigs[3], currentPatches[3]); + + currentPatches = list.finalPatches; + Assert.Equal(finalConfigs.Length, currentPatches.Count); + AssertUrlCorrect("@NODE", finalConfigs[0], currentPatches[0]); + AssertUrlCorrect("@NODE[foo]:HAS[#bar]", finalConfigs[1], currentPatches[1]); + AssertUrlCorrect("@NADE", finalConfigs[2], currentPatches[2]); + AssertUrlCorrect("@NADE", finalConfigs[3], currentPatches[3]); + + currentPatches = list.modPasses["mod1"].beforePatches; + Assert.Equal(beforeMod1Configs.Length, currentPatches.Count); + AssertUrlCorrect("@NODE", beforeMod1Configs[0], currentPatches[0]); + AssertUrlCorrect("@NODE[foo]:HAS[#bar]", beforeMod1Configs[1], currentPatches[1]); + AssertUrlCorrect("@NADE", beforeMod1Configs[2], currentPatches[2]); + AssertUrlCorrect("@NADE", beforeMod1Configs[3], currentPatches[3]); + + currentPatches = list.modPasses["mod1"].forPatches; + Assert.Equal(forMod1Configs.Length, currentPatches.Count); + AssertUrlCorrect("@NODE", forMod1Configs[0], currentPatches[0]); + AssertUrlCorrect("@NODE[foo]:HAS[#bar]", forMod1Configs[1], currentPatches[1]); + AssertUrlCorrect("@NADE", forMod1Configs[2], currentPatches[2]); + AssertUrlCorrect("@NADE", forMod1Configs[3], currentPatches[3]); + + currentPatches = list.modPasses["mod1"].afterPatches; + Assert.Equal(afterMod1Configs.Length, currentPatches.Count); + AssertUrlCorrect("@NODE", afterMod1Configs[0], currentPatches[0]); + AssertUrlCorrect("@NODE[foo]:HAS[#bar]", afterMod1Configs[1], currentPatches[1]); + AssertUrlCorrect("@NADE", afterMod1Configs[2], currentPatches[2]); + AssertUrlCorrect("@NADE", afterMod1Configs[3], currentPatches[3]); + + currentPatches = list.modPasses["mod2"].beforePatches; + Assert.Equal(beforeMod2Configs.Length, currentPatches.Count); + AssertUrlCorrect("@NODE", beforeMod2Configs[0], currentPatches[0]); + AssertUrlCorrect("@NODE[foo]:HAS[#bar]", beforeMod2Configs[1], currentPatches[1]); + AssertUrlCorrect("@NADE", beforeMod2Configs[2], currentPatches[2]); + AssertUrlCorrect("@NADE", beforeMod2Configs[3], currentPatches[3]); + + currentPatches = list.modPasses["mod2"].forPatches; + Assert.Equal(forMod1Configs.Length, currentPatches.Count); + AssertUrlCorrect("@NODE", forMod2Configs[0], currentPatches[0]); + AssertUrlCorrect("@NODE[foo]:HAS[#bar]", forMod2Configs[1], currentPatches[1]); + AssertUrlCorrect("@NADE", forMod2Configs[2], currentPatches[2]); + AssertUrlCorrect("@NADE", forMod2Configs[3], currentPatches[3]); + + currentPatches = list.modPasses["mod2"].afterPatches; + Assert.Equal(afterMod1Configs.Length, currentPatches.Count); + AssertUrlCorrect("@NODE", afterMod2Configs[0], currentPatches[0]); + AssertUrlCorrect("@NODE[foo]:HAS[#bar]", afterMod2Configs[1], currentPatches[1]); + AssertUrlCorrect("@NADE", afterMod2Configs[2], currentPatches[2]); + AssertUrlCorrect("@NADE", afterMod2Configs[3], currentPatches[3]); + } + + [Fact] + public void TestSortAndExtractPatches__InsertWithPass() + { + UrlDir.UrlConfig config1 = CreateConfig("NODE"); + UrlDir.UrlConfig config2 = CreateConfig("NODE:FOR[mod1]"); + UrlDir.UrlConfig config3 = CreateConfig("NODE:FOR[mod2]"); + UrlDir.UrlConfig config4 = CreateConfig("NODE:FINAL"); + + string[] modList = { "mod1" }; + PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + + Assert.Equal(new[] { config1 }, root.AllConfigs); + + progress.Received().Error(config2, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FOR[mod1]"); + progress.Received().Error(config3, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FOR[mod2]"); + progress.Received().Error(config4, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FINAL"); + + Assert.Empty(list.firstPatches); + Assert.Empty(list.legacyPatches); + Assert.Empty(list.finalPatches); + Assert.Empty(list.modPasses["mod1"].beforePatches); + Assert.Empty(list.modPasses["mod1"].forPatches); + Assert.Empty(list.modPasses["mod1"].afterPatches); + } + + [Fact] + public void TestSortAndExtractPatches__MoreThanOnePass() + { + UrlDir.UrlConfig config1 = CreateConfig("@NODE:FIRST"); + UrlDir.UrlConfig config2 = CreateConfig("@NODE:FIRST:FIRST"); + UrlDir.UrlConfig config3 = CreateConfig("@NODE:FIRST:FOR[mod1]"); + + string[] modList = { "mod1" }; + PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + + Assert.Empty(root.AllConfigs); + + progress.Received().Error(config2, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FIRST"); + progress.Received().Error(config3, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FOR[mod1]"); + + Assert.Equal(1, list.firstPatches.Count); + AssertUrlCorrect("@NODE", config1, list.firstPatches[0]); + Assert.Empty(list.legacyPatches); + Assert.Empty(list.finalPatches); + Assert.Empty(list.modPasses["mod1"].beforePatches); + Assert.Empty(list.modPasses["mod1"].forPatches); + Assert.Empty(list.modPasses["mod1"].afterPatches); + } + + [Fact] + public void TestSortAndExtractPatches__Exception() + { + Exception e = new Exception("an exception was thrown"); + progress.WhenForAnyArgs(p => p.Error(null, null)).Throw(e); + + UrlDir.UrlConfig config1 = CreateConfig("@NODE"); + UrlDir.UrlConfig config2 = CreateConfig("@NODE:FIRST:FIRST"); + UrlDir.UrlConfig config3 = CreateConfig("@NADE:FIRST"); + + string[] modList = { "mod1" }; + PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + + progress.Received().Exception(config2, "Exception while parsing pass for config: abc/def/@NODE:FIRST:FIRST", e); + + Assert.Equal(new[] { config1 }, list.legacyPatches); + Assert.Equal(1, list.firstPatches.Count); + AssertUrlCorrect("@NADE", config3, list.firstPatches[0]); + } + + private UrlDir.UrlConfig CreateConfig(string name) + { + ConfigNode node = new TestConfigNode(name) + { + { "name", "snack" }, + { "cheese", "gouda" }, + { "bread", "sourdough" }, + new ConfigNode("wine"), + new ConfigNode("fruit"), + }; + + node.id = "hungry?"; + + return UrlBuilder.CreateConfig(node, file); + } + + private void AssertUrlCorrect(string expectedNodeName, UrlDir.UrlConfig originalUrl, UrlDir.UrlConfig observedUrl) + { + Assert.Equal(expectedNodeName, observedUrl.type); + + ConfigNode originalNode = originalUrl.config; + ConfigNode observedNode = observedUrl.config; + + Assert.Equal(expectedNodeName, observedNode.name); + + if (originalNode.HasValue("name")) Assert.Equal(originalNode.GetValue("name"), observedUrl.name); + + Assert.Same(originalUrl.parent, observedUrl.parent); + + Assert.Equal(originalNode.id, observedNode.id); + Assert.Equal(originalNode.values.Count, observedNode.values.Count); + Assert.Equal(originalNode.nodes.Count, observedNode.nodes.Count); + + for (int i = 0; i < originalNode.values.Count; i++) + { + Assert.Same(originalNode.values[i], observedNode.values[i]); + } + + for (int i = 0; i < originalNode.nodes.Count; i++) + { + Assert.Same(originalNode.nodes[i], observedNode.nodes[i]); + } + } + } +} From 90027c2abade9c7f035948bcfed4e6cddbc32e7e Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 22:31:58 -0700 Subject: [PATCH 083/342] Add SafeUrl extension method for UrlConfig Makes sure logging doesn't mess up, and fixes the weird quirk where a node with a name value ends up displaying that instead of its actual name --- .../Extensions/UrlConfigExtensions.cs | 49 ++++++++++++ ModuleManager/ModuleManager.csproj | 1 + .../Extensions/UrlConfigExtensionsTest.cs | 79 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 4 files changed, 130 insertions(+) create mode 100644 ModuleManager/Extensions/UrlConfigExtensions.cs create mode 100644 ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs diff --git a/ModuleManager/Extensions/UrlConfigExtensions.cs b/ModuleManager/Extensions/UrlConfigExtensions.cs new file mode 100644 index 00000000..a67ab150 --- /dev/null +++ b/ModuleManager/Extensions/UrlConfigExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ModuleManager.Extensions +{ + public static class UrlConfigExtensions + { + public static string SafeUrl(this UrlDir.UrlConfig url) + { + if (url == null) return ""; + + string nodeName; + + if (!string.IsNullOrEmpty(url.type?.Trim())) + { + nodeName = url.type; + } + else if (!string.IsNullOrEmpty(url.config?.name?.Trim())) + { + nodeName = url.config.name; + } + else + { + nodeName = ""; + } + + string parentUrl = null; + + if (url.parent != null) + { + try + { + parentUrl = url.parent.url; + } + catch + { + parentUrl = ""; + } + } + + if (parentUrl == null) + return nodeName; + else + return parentUrl + "/" + nodeName; + } + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index dbe672d1..93c2bd84 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -41,6 +41,7 @@ + diff --git a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs new file mode 100644 index 00000000..23ab20d2 --- /dev/null +++ b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; +using TestUtils; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class UrlConfigExtensionsTest + { + [Fact] + public void TestSafeUrl() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "name", "this shouldn't show up" }, + }; + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def", node); + Assert.Equal("abc/def/SOME_NODE", url.SafeUrl()); + } + + [Fact] + public void TestSafeUrl__Null() + { + UrlDir.UrlConfig url = null; + Assert.Equal("", url.SafeUrl()); + } + + [Fact] + public void TestSafeUrl__NullParent() + { + UrlDir.UrlConfig url = new UrlDir.UrlConfig(null, new ConfigNode("SOME_NODE")); + Assert.Equal("SOME_NODE", url.SafeUrl()); + } + + [Fact] + public void TestSafeUrl__NullParent__NullName() + { + ConfigNode node = new ConfigNode + { + name = null + }; + UrlDir.UrlConfig url = new UrlDir.UrlConfig(null, node); + Assert.Equal("", url.SafeUrl()); + } + + [Fact] + public void TestSafeUrl__NullParent__BlankName() + { + UrlDir.UrlConfig url = new UrlDir.UrlConfig(null, new ConfigNode(" ")); + Assert.Equal("", url.SafeUrl()); + } + + [Fact] + public void TestSafeUrl__NullName() + { + ConfigNode node = new TestConfigNode() + { + { "name", "this shouldn't show up" }, + }; + node.name = null; + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def", node); + Assert.Equal("abc/def/", url.SafeUrl()); + } + + [Fact] + public void TestSafeUrl__BlankName() + { + ConfigNode node = new TestConfigNode(" ") + { + { "name", "this shouldn't show up" }, + }; + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def", node); + Assert.Equal("abc/def/", url.SafeUrl()); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 6d67d5de..0ac6f5f3 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -49,6 +49,7 @@ + From 504354d57b7ffdb5128e48cdccccd9010ed22e63 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 22:37:22 -0700 Subject: [PATCH 084/342] Use SafeUrl in logging --- ModuleManager/MMPatchLoader.cs | 8 ++++---- ModuleManager/PatchExtractor.cs | 6 +++--- ModuleManager/PatchProgress.cs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 8c0953e8..97cddeac 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -812,7 +812,7 @@ private void CheckNeeds() } catch (Exception ex) { - progress.Exception(currentMod, "Exception while checking needs : " + currentMod.url + " with a type of " + currentMod.type, ex); + progress.Exception(currentMod, "Exception while checking needs : " + currentMod.SafeUrl() + " with a type of " + currentMod.type, ex); logger.Error("Node is : " + PrettyConfig(currentMod)); } } @@ -858,7 +858,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context) if (nodeName == null) { - progress.Error(context.patchUrl, "Error - Node in file " + context.patchUrl.url + " subnode: " + stack.GetPath() + + progress.Error(context.patchUrl, "Error - Node in file " + context.patchUrl.SafeUrl() + " subnode: " + stack.GetPath() + " has config.name == null"); } @@ -1063,7 +1063,7 @@ public IEnumerator ApplyPatch(string Stage) // When this special node is found then try to apply the patch once more on the same NODE if (mod.config.HasNode("MM_PATCH_LOOP")) { - logger.Info("Looping on " + mod.url + " to " + url.url); + logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); loop = true; } } @@ -1085,7 +1085,7 @@ public IEnumerator ApplyPatch(string Stage) } catch (Exception e) { - progress.Exception(mod, "Exception while processing node : " + mod.url, e); + progress.Exception(mod, "Exception while processing node : " + mod.SafeUrl(), e); logger.Error("Processed node was\n" + PrettyConfig(mod)); mod.parent.configs.Remove(mod); } diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 3f4be072..1a7dd66e 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -49,12 +49,12 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable 0) { - progress.Error(url, $"Error - pass specifier detected on an insert node (not a patch): {url.parent.url}/{url.type}"); + progress.Error(url, $"Error - pass specifier detected on an insert node (not a patch): {url.SafeUrl()}"); error = true; } if (matchCount > 1) { - progress.Error(url, $"Error - more than one pass specifier on a node: {url.parent.url}/{url.type}"); + progress.Error(url, $"Error - more than one pass specifier on a node: {url.SafeUrl()}"); error = true; } if (error) @@ -137,7 +137,7 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable Date: Wed, 13 Sep 2017 22:38:01 -0700 Subject: [PATCH 085/342] Remove unused Doesn't really have any benefit --- ModuleManager/MMPatchLoader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 97cddeac..66bbddc7 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -647,7 +647,6 @@ private void CreateCache() node.AddValue("name", config.name); node.AddValue("type", config.type); node.AddValue("parentUrl", config.parent.url); - node.AddValue("url", config.url); node.AddNode(config.config); } @@ -746,7 +745,6 @@ private void LoadCache() string name = node.GetValue("name"); string type = node.GetValue("type"); string parentUrl = node.GetValue("parentUrl"); - string url = node.GetValue("url"); UrlDir.UrlFile parent = GameDatabase.Instance.root.AllConfigFiles.FirstOrDefault(f => f.url == parentUrl); if (parent != null) From 3f8adeeb9ca45e6afd879602d8bf5176ba6f7a65 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 23:25:12 -0700 Subject: [PATCH 086/342] Log when BEFORE or AFTER patch deleted This is pretty much equivalent to unsatisfied NEEDS, so it should be noted as such. Also log on an unsatisfied FOR, although this shouldn't happen (make it a warning) --- ModuleManager/IPatchProgress.cs | 3 ++ ModuleManager/PatchExtractor.cs | 3 ++ ModuleManager/PatchProgress.cs | 21 +++++++++ ModuleManagerTests/PatchExtractorTest.cs | 15 ++++++ ModuleManagerTests/PatchProgressTest.cs | 60 ++++++++++++++++++++++++ 5 files changed, 102 insertions(+) diff --git a/ModuleManager/IPatchProgress.cs b/ModuleManager/IPatchProgress.cs index e02cd1d0..4c20be50 100644 --- a/ModuleManager/IPatchProgress.cs +++ b/ModuleManager/IPatchProgress.cs @@ -21,6 +21,9 @@ public interface IPatchProgress void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url); void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, NodeStack path); void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, NodeStack path, string valName); + void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url); + void NeedsUnsatisfiedFor(UrlDir.UrlConfig url); + void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url); void ApplyingCopy(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); void ApplyingDelete(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 1a7dd66e..d8e0abbd 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -91,6 +91,7 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable Date: Wed, 13 Sep 2017 23:25:33 -0700 Subject: [PATCH 087/342] Fix case issues Mods may not be lowercase to begin with, need to handle this --- ModuleManager/PatchList.cs | 6 +++--- ModuleManagerTests/PatchListTest.cs | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index a120f633..6143947d 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -38,14 +38,14 @@ public ModPassCollection(IEnumerable modList) { ModPass pass = new ModPass(mod); passesArray[i] = pass; - passesDict.Add(mod, pass); + passesDict.Add(mod.ToLowerInvariant(), pass); i++; } } - public ModPass this[string name] => passesDict[name]; + public ModPass this[string name] => passesDict[name.ToLowerInvariant()]; - public bool HasMod(string name) => passesDict.ContainsKey(name); + public bool HasMod(string name) => passesDict.ContainsKey(name.ToLowerInvariant()); public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(passesArray); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index 83061aaf..dc19b426 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -21,13 +21,25 @@ public void TestConstructor() [Fact] public void TestModPasses__HasMod() { - PatchList list = new PatchList(new[] { "mod1", "mod2" }); + PatchList list = new PatchList(new[] { "mod1", "Mod2", "MOD3" }); PatchList.ModPassCollection collection = list.modPasses; Assert.True(collection.HasMod("mod1")); + Assert.True(collection.HasMod("Mod1")); + Assert.True(collection.HasMod("MOD1")); + Assert.True(collection.HasMod("mod2")); - Assert.False(collection.HasMod("mod3")); + Assert.True(collection.HasMod("Mod2")); + Assert.True(collection.HasMod("MOD2")); + + Assert.True(collection.HasMod("mod3")); + Assert.True(collection.HasMod("Mod3")); + Assert.True(collection.HasMod("MOD3")); + + Assert.False(collection.HasMod("mod4")); + Assert.False(collection.HasMod("Mod4")); + Assert.False(collection.HasMod("MOD4")); } [Fact] From 99bc0bf583693dd2d83a94d93d1043bc28d03708 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 23:49:20 -0700 Subject: [PATCH 088/342] Extract IsBracketBalanced --- ModuleManager/Extensions/StringExtensions.cs | 23 ++++++++++++ ModuleManager/MMPatchLoader.cs | 27 ++------------ ModuleManager/ModuleManager.csproj | 1 + .../Extensions/StringExtensionsTest.cs | 36 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 5 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 ModuleManager/Extensions/StringExtensions.cs create mode 100644 ModuleManagerTests/Extensions/StringExtensionsTest.cs diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs new file mode 100644 index 00000000..feaaaad2 --- /dev/null +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ModuleManager.Extensions +{ + public static class StringExtensions + { + public static bool IsBracketBalanced(this string s) + { + int level = 0; + foreach (char c in s) + { + if (c == '[') level++; + else if (c == ']') level--; + + if (level < 0) return false; + } + return level == 0; + } + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 66bbddc7..70e3028d 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -970,7 +970,7 @@ public IEnumerator ApplyPatch(string Stage) if (cmd != Command.Insert) { - if (!IsBracketBalanced(mod.type)) + if (!mod.type.IsBracketBalanced()) { progress.Error(mod, "Error - Skipping a patch with unbalanced square brackets or a space (replace them with a '?') :\n" + @@ -1413,7 +1413,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon { subMod.name = RemoveWS(subMod.name); - if (!IsBracketBalanced(subMod.name)) + if (!subMod.name.IsBracketBalanced()) { context.progress.Error(context.patchUrl, "Error - Skipping a patch subnode with unbalanced square brackets or a space (replace them with a '?') in " @@ -2068,29 +2068,6 @@ private static string FindAndReplaceValue( #region Sanity checking & Utility functions - public static bool IsBracketBalanced(string str) - { - Stack stack = new Stack(); - - char c; - for (int i = 0; i < str.Length; i++) - { - c = str[i]; - if (c == '[') - stack.Push(c); - else if (c == ']') - { - if (stack.Count == 0) - return false; - if (stack.Peek() == '[') - stack.Pop(); - else - return false; - } - } - return stack.Count == 0; - } - public static string RemoveWS(string withWhite) { // Removes ALL whitespace of a string. diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 93c2bd84..963e432c 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -41,6 +41,7 @@ + diff --git a/ModuleManagerTests/Extensions/StringExtensionsTest.cs b/ModuleManagerTests/Extensions/StringExtensionsTest.cs new file mode 100644 index 00000000..4267648b --- /dev/null +++ b/ModuleManagerTests/Extensions/StringExtensionsTest.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class StringExtensionsTest + { + [Fact] + public void TestIsBracketBalanced() + { + Assert.True("abc[def[ghi[jkl]mno[pqr]]stu]vwx".IsBracketBalanced()); + } + + [Fact] + public void TestIsBracketBalanced__NoBrackets() + { + Assert.True("she sells seashells by the seashore".IsBracketBalanced()); + } + + [Fact] + public void TestIsBracketBalanced__Unbalanced() + { + Assert.False("abc[def[ghi[jkl]mno[pqr]]stuvwx".IsBracketBalanced()); + Assert.False("abcdef[ghi[jkl]mno[pqr]]stu]vwx".IsBracketBalanced()); + } + [Fact] + public void TestIsBracketBalanced__BalancedButNegative() + { + Assert.False("abc]def[ghi".IsBracketBalanced()); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 0ac6f5f3..dea5c743 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -49,6 +49,7 @@ + From e52a646ba73fb0639218d779508e7f110aaa1a3f Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 13 Sep 2017 23:56:29 -0700 Subject: [PATCH 089/342] Remove bracket unbalanced nodes when sorting --- ModuleManager/PatchExtractor.cs | 7 +++++++ ModuleManagerTests/PatchExtractorTest.cs | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index d8e0abbd..ebf7ee3f 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -23,6 +23,13 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable Date: Wed, 13 Sep 2017 23:57:46 -0700 Subject: [PATCH 090/342] Unused method --- ModuleManager/MMPatchLoader.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 70e3028d..ca1e2087 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -2074,11 +2074,6 @@ public static string RemoveWS(string withWhite) return new string(withWhite.ToCharArray().Where(c => !Char.IsWhiteSpace(c)).ToArray()); } - public bool IsPathInList(string modPath, List pathList) - { - return pathList.Any(modPath.StartsWith); - } - #endregion Sanity checking & Utility functions #region Condition checking From 5a468bea8d010631895e3e7bdbf40a5682748b13 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 14 Sep 2017 00:31:54 -0700 Subject: [PATCH 091/342] Bring back DeepCopy Apparently KSP's default implementation fails on badly formed nodes --- .../Extensions/ConfigNodeExtensions.cs | 13 ++++ ModuleManager/MMPatchLoader.cs | 2 +- .../Extensions/ConfigNodeExtensionsTest.cs | 62 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index bf876692..8347f5a7 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -15,5 +15,18 @@ public static void ShallowCopyFrom(this ConfigNode toNode, ConfigNode fromeNode) foreach (ConfigNode node in fromeNode.nodes) toNode.nodes.Add(node); } + + public static ConfigNode DeepCopy(this ConfigNode from) + { + ConfigNode to = new ConfigNode(from.name); + foreach (ConfigNode.Value value in from.values) + to.AddValue(value.name, value.value); + foreach (ConfigNode node in from.nodes) + { + ConfigNode newNode = DeepCopy(node); + to.nodes.Add(newNode); + } + return to; + } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index ca1e2087..b5d9cd9f 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1108,7 +1108,7 @@ public IEnumerator ApplyPatch(string Stage) // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse. public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchContext context) { - ConfigNode newNode = original.value.CreateCopy(); + ConfigNode newNode = original.value.DeepCopy(); NodeStack nodeStack = original.ReplaceValue(newNode); #region Values diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index d48c2b2f..3ae936b9 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -83,5 +83,67 @@ public void TestShallowCopyFrom() Assert.Equal(0, innerNode2.nodes[0].values.Count); Assert.Equal(0, innerNode2.nodes[0].nodes.Count); } + + [Fact] + public void TestDeepCopy() + { + ConfigNode fromNode = new TestConfigNode("SOME_NODE") + { + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE_1") + { + { "mno", "pqr" }, + new TestConfigNode("INNER_INNER_NODE_1"), + }, + new TestConfigNode("INNER_NODE_2") + { + { "stu", "vwx" }, + new TestConfigNode("INNER_INNER_NODE_2"), + }, + }; + + ConfigNode toNode = fromNode.DeepCopy(); + + Assert.Equal("SOME_NODE", toNode.name); + + Assert.Equal(2, toNode.values.Count); + + Assert.NotSame(fromNode.values[0], toNode.values[0]); + Assert.Equal("abc", toNode.values[0].name); + Assert.Equal("def", toNode.values[0].value); + + Assert.NotSame(fromNode.values[1], toNode.values[1]); + Assert.Equal("ghi", toNode.values[1].name); + Assert.Equal("jkl", toNode.values[1].value); + + Assert.Equal(2, toNode.nodes.Count); + + ConfigNode innerNode1 = toNode.nodes[0]; + Assert.NotSame(fromNode.nodes[0], innerNode1); + Assert.Equal("INNER_NODE_1", innerNode1.name); + Assert.Equal(1, innerNode1.values.Count); + Assert.NotSame(fromNode.nodes[0].values[0], innerNode1.values[0]); + Assert.Equal("mno", innerNode1.values[0].name); + Assert.Equal("pqr", innerNode1.values[0].value); + Assert.Equal(1, toNode.nodes[0].nodes.Count); + Assert.NotSame(fromNode.nodes[0].nodes[0], innerNode1.nodes[0]); + Assert.Equal("INNER_INNER_NODE_1", innerNode1.nodes[0].name); + Assert.Equal(0, innerNode1.nodes[0].values.Count); + Assert.Equal(0, innerNode1.nodes[0].nodes.Count); + + ConfigNode innerNode2 = toNode.nodes[1]; + Assert.NotSame(fromNode.nodes[1], innerNode2); + Assert.Equal("INNER_NODE_2", innerNode2.name); + Assert.Equal(1, innerNode2.values.Count); + Assert.NotSame(fromNode.nodes[1].values[0], innerNode2.values[0]); + Assert.Equal("stu", innerNode2.values[0].name); + Assert.Equal("vwx", innerNode2.values[0].value); + Assert.Equal(1, innerNode2.nodes.Count); + Assert.NotSame(fromNode.nodes[1].nodes[0], innerNode2.nodes[0]); + Assert.Equal("INNER_INNER_NODE_2", innerNode2.nodes[0].name); + Assert.Equal(0, innerNode2.nodes[0].values.Count); + Assert.Equal(0, innerNode2.nodes[0].nodes.Count); + } } } From 2e5854df9056d21d8efcac868de3d527e1ddd319 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 14 Sep 2017 00:41:32 -0700 Subject: [PATCH 092/342] Fix bad region --- ModuleManager/MMPatchLoader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index b5d9cd9f..3146d22d 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -307,7 +307,7 @@ private IEnumerator ProcessPatch() #endregion Applying patches - #region Logging + #region Saving Cache if (progress.ErrorCount > 0 || progress.ExceptionCount > 0) { @@ -337,7 +337,9 @@ private IEnumerator ProcessPatch() yield return null; CreateCache(); } - + + #endregion Saving Cache + SaveModdedTechTree(); SaveModdedPhysics(); } @@ -353,8 +355,6 @@ private IEnumerator ProcessPatch() logger.Info(status + "\n" + errors); - #endregion Logging - #if DEBUG RunTestCases(); #endif From 9bd8253f26f0aaf03891cbef10d5a3c27de3ec6d Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 14 Sep 2017 21:28:31 -0700 Subject: [PATCH 093/342] Make sure badly formed mod passes are an error --- ModuleManager/PatchExtractor.cs | 21 +++++++++++++++--- ModuleManagerTests/PatchExtractorTest.cs | 27 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index ebf7ee3f..34ac1529 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -11,9 +11,9 @@ public static class PatchExtractor { private static readonly Regex firstRegex = new Regex(@":FIRST", RegexOptions.IgnoreCase); private static readonly Regex finalRegex = new Regex(@":FINAL", RegexOptions.IgnoreCase); - private static readonly Regex beforeRegex = new Regex(@":BEFORE\[([^\[\]]+)\]", RegexOptions.IgnoreCase); - private static readonly Regex forRegex = new Regex(@":FOR\[([^\[\]]+)\]", RegexOptions.IgnoreCase); - private static readonly Regex afterRegex = new Regex(@":AFTER\[([^\[\]]+)\]", RegexOptions.IgnoreCase); + private static readonly Regex beforeRegex = new Regex(@":BEFORE(?:\[([^\[\]]+)\])?", RegexOptions.IgnoreCase); + private static readonly Regex forRegex = new Regex(@":FOR(?:\[([^\[\]]+)\])?", RegexOptions.IgnoreCase); + private static readonly Regex afterRegex = new Regex(@":AFTER(?:\[([^\[\]]+)\])?", RegexOptions.IgnoreCase); public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable modList, IPatchProgress progress) { @@ -64,6 +64,21 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable Date: Thu, 14 Sep 2017 22:20:38 -0700 Subject: [PATCH 094/342] That's a bug --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 3146d22d..6ffe1304 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1454,7 +1454,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon //string newName = subName.Substring(0, start); //string path = subName.Substring(start + 1, end - start - 1); - ConfigNode toPaste = RecurseNodeSearch(subName.Substring(1), nodeStack.Pop(), context); + ConfigNode toPaste = RecurseNodeSearch(subName.Substring(1), nodeStack, context); if (toPaste == null) { From cacc840da0d34307ef18796288eaafb30d2f3229 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 14 Sep 2017 22:57:14 -0700 Subject: [PATCH 095/342] Add some explanatory comments --- ModuleManager/Extensions/ConfigNodeExtensions.cs | 1 + ModuleManager/PatchExtractor.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 8347f5a7..12343601 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -16,6 +16,7 @@ public static void ShallowCopyFrom(this ConfigNode toNode, ConfigNode fromeNode) toNode.nodes.Add(node); } + // KSP implementation of ConfigNode.CreateCopy breaks with badly formed nodes (nodes with a blank name) public static ConfigNode DeepCopy(this ConfigNode from) { ConfigNode to = new ConfigNode(from.name); diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 34ac1529..64557dff 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -19,6 +19,7 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable Date: Thu, 14 Sep 2017 22:57:56 -0700 Subject: [PATCH 096/342] Unnecessary using directives --- ModuleManager/Extensions/ConfigNodeExtensions.cs | 3 --- ModuleManager/PatchExtractor.cs | 1 - ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs | 3 --- 3 files changed, 7 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 12343601..5174b356 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace ModuleManager.Extensions { diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 64557dff..41b05701 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using ModuleManager.Extensions; diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index 3ae936b9..5587b488 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Xunit; using TestUtils; using ModuleManager.Extensions; From 8d71ac691054fcc9e1fa236d4debbb2687be4632 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 14 Sep 2017 23:12:35 -0700 Subject: [PATCH 097/342] Use sorted patches when applying Improves performance somewhat Verified that sorting patches takes almost no time even for a fairly large number of patches --- ModuleManager/MMPatchLoader.cs | 57 +++++++++++++--------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 6ffe1304..c2fe14c1 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -279,6 +279,17 @@ private IEnumerator ProcessPatch() #endregion Check Needs + #region Sorting Patches + + status = "Sorting patches"; + logger.Info(status); + + yield return null; + + PatchList patchList = PatchExtractor.SortAndExtractPatches(GameDatabase.Instance.root, mods, progress); + + #endregion + #region Applying patches status = "Applying patches"; @@ -287,21 +298,21 @@ private IEnumerator ProcessPatch() yield return null; // :First node - yield return StartCoroutine(ApplyPatch(":FIRST")); + yield return StartCoroutine(ApplyPatch(":FIRST", patchList.firstPatches)); // any node without a :pass - yield return StartCoroutine(ApplyPatch(":LEGACY")); + yield return StartCoroutine(ApplyPatch(":LEGACY", patchList.legacyPatches)); - foreach (string mod in mods) + foreach (PatchList.ModPass pass in patchList.modPasses) { - string upperModName = mod.ToUpper(); - yield return StartCoroutine(ApplyPatch(":BEFORE[" + upperModName + "]")); - yield return StartCoroutine(ApplyPatch(":FOR[" + upperModName + "]")); - yield return StartCoroutine(ApplyPatch(":AFTER[" + upperModName + "]")); + string upperModName = pass.name.ToUpper(); + yield return StartCoroutine(ApplyPatch(":BEFORE[" + upperModName + "]", pass.beforePatches)); + yield return StartCoroutine(ApplyPatch(":FOR[" + upperModName + "]", pass.forPatches)); + yield return StartCoroutine(ApplyPatch(":AFTER[" + upperModName + "]", pass.afterPatches)); } // :Final node - yield return StartCoroutine(ApplyPatch(":FINAL")); + yield return StartCoroutine(ApplyPatch(":FINAL", patchList.finalPatches)); PurgeUnused(); @@ -948,7 +959,7 @@ private void PurgeUnused() #region Applying Patches // Apply patch to all relevent nodes - public IEnumerator ApplyPatch(string Stage) + public IEnumerator ApplyPatch(string Stage, IEnumerable patches) { StatusUpdate(); logger.Info(Stage + (Stage == ":LEGACY" ? " (default) pass" : " pass")); @@ -960,9 +971,8 @@ public IEnumerator ApplyPatch(string Stage) float nextYield = Time.realtimeSinceStartup + yieldInterval; - for (int modsIndex = 0; modsIndex < allConfigs.Length; modsIndex++) + foreach (UrlDir.UrlConfig mod in patches) { - UrlDir.UrlConfig mod = allConfigs[modsIndex]; try { string name = RemoveWS(mod.type); @@ -970,31 +980,8 @@ public IEnumerator ApplyPatch(string Stage) if (cmd != Command.Insert) { - if (!mod.type.IsBracketBalanced()) - { - progress.Error(mod, - "Error - Skipping a patch with unbalanced square brackets or a space (replace them with a '?') :\n" + - mod.name + "\n"); - - // And remove it so it's not tried anymore - mod.parent.configs.Remove(mod); - continue; - } - - // Ensure the stage is correct string upperName = name.ToUpper(); - int stageIdx = upperName.IndexOf(Stage); - if (stageIdx >= 0) - name = name.Substring(0, stageIdx) + name.Substring(stageIdx + Stage.Length); - else if ( - !((upperName.Contains(":FIRST") || Stage == ":LEGACY") - && !upperName.Contains(":BEFORE[") && !upperName.Contains(":FOR[") - && !upperName.Contains(":AFTER[") && !upperName.Contains(":FINAL"))) - continue; - - // TODO: do we want to ensure there's only one phase specifier? - try { PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); @@ -1076,8 +1063,6 @@ public IEnumerator ApplyPatch(string Stage) } finally { - // The patch was either run or has failed, in any case let's remove it from the database - mod.parent.configs.Remove(mod); } } } From aa990db608166c83fd2fdbf9cd62fdba40788ff8 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 14 Sep 2017 23:17:44 -0700 Subject: [PATCH 098/342] Remove now-unnecessary try-catch There's already one around it and we no longer care about removing patches from the database at this stage --- ModuleManager/MMPatchLoader.cs | 131 ++++++++++++++++----------------- 1 file changed, 62 insertions(+), 69 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index c2fe14c1..55b28c61 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -981,89 +981,82 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche if (cmd != Command.Insert) { string upperName = name.ToUpper(); + PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); + char[] sep = { '[', ']' }; + string condition = ""; - try + if (upperName.Contains(":HAS[")) { - PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); - char[] sep = { '[', ']' }; - string condition = ""; - - if (upperName.Contains(":HAS[")) - { - int start = upperName.IndexOf(":HAS["); - condition = name.Substring(start + 5, name.LastIndexOf(']') - start - 5); - name = name.Substring(0, start); - } + int start = upperName.IndexOf(":HAS["); + condition = name.Substring(start + 5, name.LastIndexOf(']') - start - 5); + name = name.Substring(0, start); + } - string[] splits = name.Split(sep, 3); - string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; - string type = splits[0].Substring(1); + string[] splits = name.Split(sep, 3); + string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; + string type = splits[0].Substring(1); - foreach (UrlDir.UrlConfig url in GameDatabase.Instance.root.AllConfigs.ToArray()) + foreach (UrlDir.UrlConfig url in GameDatabase.Instance.root.AllConfigs.ToArray()) + { + foreach (string pattern in patterns) { - foreach (string pattern in patterns) + bool loop = false; + do { - bool loop = false; - do + if (url.type == type && WildcardMatch(url.name, pattern) + && CheckConstraints(url.config, condition)) { - if (url.type == type && WildcardMatch(url.name, pattern) - && CheckConstraints(url.config, condition)) + switch (cmd) { - switch (cmd) - { - case Command.Edit: - progress.ApplyingUpdate(url, mod); - url.config = ModifyNode(new NodeStack(url.config), mod.config, context); - break; - - case Command.Copy: - ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); - if (url.config.name != mod.name) - { - progress.ApplyingCopy(url, mod); - url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); - } - else - { - progress.Error(mod, "Error - Error while processing " + mod.config.name + - " the copy needs to have a different name than the parent (use @name = xxx)"); - } - break; - - case Command.Delete: - progress.ApplyingDelete(url, mod); - url.parent.configs.Remove(url); - break; - - case Command.Replace: - - // TODO: do something sensible here. - break; - - case Command.Create: - - // TODO: something similar to above - break; - } - // When this special node is found then try to apply the patch once more on the same NODE - if (mod.config.HasNode("MM_PATCH_LOOP")) - { - logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); - loop = true; - } + case Command.Edit: + progress.ApplyingUpdate(url, mod); + url.config = ModifyNode(new NodeStack(url.config), mod.config, context); + break; + + case Command.Copy: + ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); + if (url.config.name != mod.name) + { + progress.ApplyingCopy(url, mod); + url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); + } + else + { + progress.Error(mod, "Error - Error while processing " + mod.config.name + + " the copy needs to have a different name than the parent (use @name = xxx)"); + } + break; + + case Command.Delete: + progress.ApplyingDelete(url, mod); + url.parent.configs.Remove(url); + break; + + case Command.Replace: + + // TODO: do something sensible here. + break; + + case Command.Create: + + // TODO: something similar to above + break; } - else + // When this special node is found then try to apply the patch once more on the same NODE + if (mod.config.HasNode("MM_PATCH_LOOP")) { - loop = false; + logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); + loop = true; } - } while (loop); + } + else + { + loop = false; + } + } while (loop); - } } } - finally - { - } } } catch (Exception e) From 3ca180142d1cc787cf92ceae683b64cbab04c964 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 14 Sep 2017 23:21:19 -0700 Subject: [PATCH 099/342] Replace big if with guard clause Reduces indentation. Insert nodes shouldn't exist here anyway --- ModuleManager/MMPatchLoader.cs | 135 +++++++++++++++++---------------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 55b28c61..04a18ce2 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -978,84 +978,87 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche string name = RemoveWS(mod.type); Command cmd = CommandParser.Parse(name, out string tmp); - if (cmd != Command.Insert) + if (cmd == Command.Insert) { - string upperName = name.ToUpper(); - PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); - char[] sep = { '[', ']' }; - string condition = ""; + Debug.LogWarning("Warning - Encountered insert node that should not exist at this stage: " + mod.SafeUrl()); + continue; + } - if (upperName.Contains(":HAS[")) - { - int start = upperName.IndexOf(":HAS["); - condition = name.Substring(start + 5, name.LastIndexOf(']') - start - 5); - name = name.Substring(0, start); - } + string upperName = name.ToUpper(); + PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); + char[] sep = { '[', ']' }; + string condition = ""; + + if (upperName.Contains(":HAS[")) + { + int start = upperName.IndexOf(":HAS["); + condition = name.Substring(start + 5, name.LastIndexOf(']') - start - 5); + name = name.Substring(0, start); + } - string[] splits = name.Split(sep, 3); - string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; - string type = splits[0].Substring(1); + string[] splits = name.Split(sep, 3); + string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; + string type = splits[0].Substring(1); - foreach (UrlDir.UrlConfig url in GameDatabase.Instance.root.AllConfigs.ToArray()) + foreach (UrlDir.UrlConfig url in GameDatabase.Instance.root.AllConfigs.ToArray()) + { + foreach (string pattern in patterns) { - foreach (string pattern in patterns) + bool loop = false; + do { - bool loop = false; - do + if (url.type == type && WildcardMatch(url.name, pattern) + && CheckConstraints(url.config, condition)) { - if (url.type == type && WildcardMatch(url.name, pattern) - && CheckConstraints(url.config, condition)) + switch (cmd) { - switch (cmd) - { - case Command.Edit: - progress.ApplyingUpdate(url, mod); - url.config = ModifyNode(new NodeStack(url.config), mod.config, context); - break; - - case Command.Copy: - ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); - if (url.config.name != mod.name) - { - progress.ApplyingCopy(url, mod); - url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); - } - else - { - progress.Error(mod, "Error - Error while processing " + mod.config.name + - " the copy needs to have a different name than the parent (use @name = xxx)"); - } - break; - - case Command.Delete: - progress.ApplyingDelete(url, mod); - url.parent.configs.Remove(url); - break; - - case Command.Replace: - - // TODO: do something sensible here. - break; - - case Command.Create: - - // TODO: something similar to above - break; - } - // When this special node is found then try to apply the patch once more on the same NODE - if (mod.config.HasNode("MM_PATCH_LOOP")) - { - logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); - loop = true; - } + case Command.Edit: + progress.ApplyingUpdate(url, mod); + url.config = ModifyNode(new NodeStack(url.config), mod.config, context); + break; + + case Command.Copy: + ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); + if (url.config.name != mod.name) + { + progress.ApplyingCopy(url, mod); + url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); + } + else + { + progress.Error(mod, "Error - Error while processing " + mod.config.name + + " the copy needs to have a different name than the parent (use @name = xxx)"); + } + break; + + case Command.Delete: + progress.ApplyingDelete(url, mod); + url.parent.configs.Remove(url); + break; + + case Command.Replace: + + // TODO: do something sensible here. + break; + + case Command.Create: + + // TODO: something similar to above + break; } - else + // When this special node is found then try to apply the patch once more on the same NODE + if (mod.config.HasNode("MM_PATCH_LOOP")) { - loop = false; + logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); + loop = true; } - } while (loop); + } + else + { + loop = false; + } + } while (loop); - } } } } From ec7c67cea50bc2b67ceadeac9b57ae3284816a24 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 14 Sep 2017 23:24:55 -0700 Subject: [PATCH 100/342] Simplify this It no longer has to look in actual passes here, so we can just use the name we want it to display. It does change the way it displays in the loading screen but that seems fine. --- ModuleManager/MMPatchLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 04a18ce2..45e79e59 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -301,7 +301,7 @@ private IEnumerator ProcessPatch() yield return StartCoroutine(ApplyPatch(":FIRST", patchList.firstPatches)); // any node without a :pass - yield return StartCoroutine(ApplyPatch(":LEGACY", patchList.legacyPatches)); + yield return StartCoroutine(ApplyPatch(":LEGACY (default)", patchList.legacyPatches)); foreach (PatchList.ModPass pass in patchList.modPasses) { @@ -962,7 +962,7 @@ private void PurgeUnused() public IEnumerator ApplyPatch(string Stage, IEnumerable patches) { StatusUpdate(); - logger.Info(Stage + (Stage == ":LEGACY" ? " (default) pass" : " pass")); + logger.Info(Stage + " pass"); yield return null; activity = "ModuleManager " + Stage; From 809831304b89671c1abf746c91f5d166dd5a1a86 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 15 Sep 2017 00:02:04 -0700 Subject: [PATCH 101/342] Invalid command = error on the patch extractor This seems like the right place to check it --- ModuleManager/MMPatchLoader.cs | 10 ++------ ModuleManager/PatchExtractor.cs | 26 ++++++++++++++++++++ ModuleManagerTests/PatchExtractorTest.cs | 30 ++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 45e79e59..79cdf94c 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1036,14 +1036,8 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche url.parent.configs.Remove(url); break; - case Command.Replace: - - // TODO: do something sensible here. - break; - - case Command.Create: - - // TODO: something similar to above + default: + logger.Warning("Invalid command encountered on a root node: " + mod.SafeUrl()); break; } // When this special node is found then try to apply the patch once more on the same NODE diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 41b05701..2283ba0b 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -59,6 +59,32 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable 1) { progress.Error(url, $"Error - more than one pass specifier on a node: {url.SafeUrl()}"); diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 2935e8a6..89cbbce9 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -334,6 +334,36 @@ public void TestSortAndExtractPatches__BadlyFormed() Assert.Empty(list.modPasses["mod1"].afterPatches); } + [Fact] + public void TestSortAndExtractPatches__InvalidCommand() + { + UrlDir.UrlConfig config1 = CreateConfig("@NODE:FOR[mod1]"); + UrlDir.UrlConfig config2 = CreateConfig("%NODE:FOR[mod1]"); + UrlDir.UrlConfig config3 = CreateConfig("&NODE:FOR[mod1]"); + UrlDir.UrlConfig config4 = CreateConfig("|NODE:FOR[mod1]"); + UrlDir.UrlConfig config5 = CreateConfig("#NODE:FOR[mod1]"); + UrlDir.UrlConfig config6 = CreateConfig("*NODE:FOR[mod1]"); + + string[] modList = { "mod1" }; + PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + + Assert.Empty(root.AllConfigs); + + progress.Received().Error(config2, "Error - replace command (%) is not valid on a root node: abc/def/%NODE:FOR[mod1]"); + progress.Received().Error(config3, "Error - create command (&) is not valid on a root node: abc/def/&NODE:FOR[mod1]"); + progress.Received().Error(config4, "Error - rename command (|) is not valid on a root node: abc/def/|NODE:FOR[mod1]"); + progress.Received().Error(config5, "Error - paste command (#) is not valid on a root node: abc/def/#NODE:FOR[mod1]"); + progress.Received().Error(config6, "Error - special command (*) is not valid on a root node: abc/def/*NODE:FOR[mod1]"); + + Assert.Empty(list.firstPatches); + Assert.Empty(list.legacyPatches); + Assert.Empty(list.finalPatches); + Assert.Empty(list.modPasses["mod1"].beforePatches); + Assert.Equal(1, list.modPasses["mod1"].forPatches.Count); + AssertUrlCorrect("@NODE", config1, list.modPasses["mod1"].forPatches[0]); + Assert.Empty(list.modPasses["mod1"].afterPatches); + } + private UrlDir.UrlConfig CreateConfig(string name) { ConfigNode node = new TestConfigNode(name) From 180c2ee8434b8558072fca973299fa4821969f50 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 18 Sep 2017 21:35:25 -0700 Subject: [PATCH 102/342] Extract RemoveWS --- ModuleManager/Extensions/StringExtensions.cs | 11 ++++++--- ModuleManager/MMPatchLoader.cs | 24 ++++++------------- .../Extensions/StringExtensionsTest.cs | 7 ++++++ 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index feaaaad2..c4190045 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Text.RegularExpressions; namespace ModuleManager.Extensions { @@ -19,5 +17,12 @@ public static bool IsBracketBalanced(this string s) } return level == 0; } + + private static Regex whitespaceRegex = new Regex(@"\s+"); + + public static string RemoveWS(this string withWhite) + { + return whitespaceRegex.Replace(withWhite, ""); + } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 79cdf94c..3491a7cc 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -189,7 +189,7 @@ private void PrePatchInit() progress.PatchAdded(); if (name.Contains(":FOR[")) { - name = RemoveWS(name); + name = name.RemoveWS(); // check for FOR[] blocks that don't match loaded DLLs and add them to the pass list try @@ -216,7 +216,7 @@ private void PrePatchInit() foreach (string subdir in Directory.GetDirectories(gameData)) { string name = Path.GetFileName(subdir); - string cleanName = RemoveWS(name); + string cleanName = name.RemoveWS(); if (!mods.Contains(cleanName, StringComparer.OrdinalIgnoreCase)) { mods.Add(cleanName); @@ -947,7 +947,7 @@ private void PurgeUnused() { foreach (UrlDir.UrlConfig mod in GameDatabase.Instance.root.AllConfigs.ToArray()) { - string name = RemoveWS(mod.type); + string name = mod.type.RemoveWS(); if (CommandParser.Parse(name, out name) != Command.Insert) mod.parent.configs.Remove(mod); @@ -975,7 +975,7 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche { try { - string name = RemoveWS(mod.type); + string name = mod.type.RemoveWS(); Command cmd = CommandParser.Parse(name, out string tmp); if (cmd == Command.Insert) @@ -1386,7 +1386,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon foreach (ConfigNode subMod in mod.nodes) { - subMod.name = RemoveWS(subMod.name); + subMod.name = subMod.name.RemoveWS(); if (!subMod.name.IsBracketBalanced()) { @@ -2041,22 +2041,12 @@ private static string FindAndReplaceValue( #endregion Applying Patches - #region Sanity checking & Utility functions - - public static string RemoveWS(string withWhite) - { - // Removes ALL whitespace of a string. - return new string(withWhite.ToCharArray().Where(c => !Char.IsWhiteSpace(c)).ToArray()); - } - - #endregion Sanity checking & Utility functions - #region Condition checking // Split condiction while not getting lost in embeded brackets public static List SplitConstraints(string condition) { - condition = RemoveWS(condition) + ","; + condition = condition.RemoveWS() + ","; List conditions = new List(); int start = 0; int level = 0; @@ -2079,7 +2069,7 @@ public static List SplitConstraints(string condition) public static bool CheckConstraints(ConfigNode node, string constraints) { - constraints = RemoveWS(constraints); + constraints = constraints.RemoveWS(); if (constraints.Length == 0) return true; diff --git a/ModuleManagerTests/Extensions/StringExtensionsTest.cs b/ModuleManagerTests/Extensions/StringExtensionsTest.cs index 4267648b..fdf3b33f 100644 --- a/ModuleManagerTests/Extensions/StringExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/StringExtensionsTest.cs @@ -27,10 +27,17 @@ public void TestIsBracketBalanced__Unbalanced() Assert.False("abc[def[ghi[jkl]mno[pqr]]stuvwx".IsBracketBalanced()); Assert.False("abcdef[ghi[jkl]mno[pqr]]stu]vwx".IsBracketBalanced()); } + [Fact] public void TestIsBracketBalanced__BalancedButNegative() { Assert.False("abc]def[ghi".IsBracketBalanced()); } + + [Fact] + public void TestRemoveWS() + { + Assert.Equal("abcdef", " abc \tdef\r\n\t ".RemoveWS()); + } } } From 20a8afbeaf2aae223c407899649360715249d102 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 18 Sep 2017 23:08:32 -0700 Subject: [PATCH 103/342] Fix logging --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 3491a7cc..3826161e 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -980,7 +980,7 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche if (cmd == Command.Insert) { - Debug.LogWarning("Warning - Encountered insert node that should not exist at this stage: " + mod.SafeUrl()); + logger.Warning("Warning - Encountered insert node that should not exist at this stage: " + mod.SafeUrl()); continue; } From 35d89f8ab11a495ade7d25719e367b903127a4fa Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 19 Sep 2017 21:10:40 -0700 Subject: [PATCH 104/342] Extract PrettyPrint --- .../Extensions/ConfigNodeExtensions.cs | 42 ++++++ ModuleManager/MMPatchLoader.cs | 56 +------- .../Extensions/ConfigNodeExtensionsTest.cs | 132 ++++++++++++++++++ 3 files changed, 175 insertions(+), 55 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 5174b356..55d900f0 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace ModuleManager.Extensions { @@ -26,5 +27,46 @@ public static ConfigNode DeepCopy(this ConfigNode from) } return to; } + + public static void PrettyPrint(this ConfigNode node, ref StringBuilder sb, string indent) + { + if (sb == null) throw new ArgumentNullException(nameof(sb)); + if (indent == null) indent = string.Empty; + if (node == null) + { + sb.Append(indent + ""); + return; + } + sb.AppendFormat("{0}{1}\n{2}{{\n", indent, node.name ?? "", indent); + string newindent = indent + " "; + if (node.values == null) + { + sb.AppendFormat("{0}\n", newindent); + } + else + { + foreach (ConfigNode.Value value in node.values) + { + if (value == null) + sb.AppendFormat("{0}\n", newindent); + else + sb.AppendFormat("{0}{1} = {2}\n", newindent, value.name ?? "", value.value ?? ""); + } + } + + if (node.nodes == null) + { + sb.AppendFormat("{0}\n", newindent); + } + else + { + foreach (ConfigNode subnode in node.nodes) + { + subnode.PrettyPrint(ref sb, newindent); + } + } + + sb.AppendFormat("{0}}}\n", indent); + } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 3826161e..fc469574 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -2247,7 +2247,7 @@ private string PrettyConfig(UrlDir.UrlConfig config) { if (config.config != null) { - PrettyConfig(config.config, ref sb, " "); + config.config.PrettyPrint(ref sb, " "); } else { @@ -2262,60 +2262,6 @@ private string PrettyConfig(UrlDir.UrlConfig config) return sb.ToString(); } - private void PrettyConfig(ConfigNode node, ref StringBuilder sb, string indent) - { - sb.AppendFormat("{0}{1}\n{2}{{\n", indent, node.name ?? "NULL", indent); - string newindent = indent + " "; - if (node.values != null) - { - foreach (ConfigNode.Value value in node.values) - { - if (value != null) - { - try - { - sb.AppendFormat("{0}{1} = {2}\n", newindent, value.name ?? "null", value.value ?? "null"); - } - catch (Exception) - { - logger.Error("value.name.Length=" + value.name.Length); - logger.Error("value.name.IsNullOrEmpty=" + string.IsNullOrEmpty(value.name)); - logger.Error("n " + value.name); - logger.Error("v " + value.value); - throw; - } - } - else - { - sb.AppendFormat("{0} Null value\n", newindent); - } - } - } - else - { - sb.AppendFormat("{0} Null values\n", newindent); - } - if (node.nodes != null) - { - foreach (ConfigNode subnode in node.nodes) - { - if (subnode != null) - { - PrettyConfig(subnode, ref sb, newindent); - } - else - { - sb.AppendFormat("{0} Null Subnode\n", newindent); - } - } - } - else - { - sb.AppendFormat("{0} Null nodes\n", newindent); - } - sb.AppendFormat("{0}}}\n", indent); - } - //FindConfigNodeIn finds and returns a ConfigNode in src of type nodeType. //If nodeName is not null, it will only find a node of type nodeType with the value name=nodeName. //If nodeTag is not null, it will only find a node of type nodeType with the value name=nodeName and tag=nodeTag. diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index 5587b488..e63e54a2 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using Xunit; using TestUtils; using ModuleManager.Extensions; @@ -142,5 +143,136 @@ public void TestDeepCopy() Assert.Equal(0, innerNode2.nodes[0].values.Count); Assert.Equal(0, innerNode2.nodes[0].nodes.Count); } + + [Fact] + public void TestPrettyPrint() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE_1") + { + { "mno", "pqr" }, + new TestConfigNode("INNER_INNER_NODE_1"), + }, + new TestConfigNode("INNER_NODE_2") + { + { "stu", "vwx" }, + new TestConfigNode("INNER_INNER_NODE_2"), + }, + }; + + string expected = @" +XXSOME_NODE +XX{ +XX abc = def +XX ghi = jkl +XX INNER_NODE_1 +XX { +XX mno = pqr +XX INNER_INNER_NODE_1 +XX { +XX } +XX } +XX INNER_NODE_2 +XX { +XX stu = vwx +XX INNER_INNER_NODE_2 +XX { +XX } +XX } +XX} +".TrimStart().Replace("\r", null); + + StringBuilder sb = new StringBuilder(); + node.PrettyPrint(ref sb, "XX"); + + Assert.Equal(expected, sb.ToString()); + } + + [Fact] + public void TestPrettyPrint__NullNode() + { + ConfigNode node = null; + StringBuilder sb = new StringBuilder(); + node.PrettyPrint(ref sb, "XX"); + Assert.Equal("XX", sb.ToString()); + } + + [Fact] + public void TestPrettyPrint__NullStringBuilder() + { + ConfigNode node = new ConfigNode("NODE"); + StringBuilder sb = null; + Assert.Throws(delegate + { + node.PrettyPrint(ref sb, "XX"); + }); + } + + [Fact] + public void TestPrettyPrint__NullIndent() + { + ConfigNode node = new TestConfigNode() + { + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE") + { + { "mno", "pqr" }, + }, + }; + + node.name = null; + + string expected = @" + +{ + abc = def + ghi = jkl + INNER_NODE + { + mno = pqr + } +} +".TrimStart().Replace("\r", null); + + StringBuilder sb = new StringBuilder(); + node.PrettyPrint(ref sb, null); + Assert.Equal(expected, sb.ToString()); + } + + [Fact] + public void TestPrettyPrint__NullName() + { + ConfigNode node = new TestConfigNode() + { + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE") + { + { "mno", "pqr" }, + }, + }; + + node.name = null; + + string expected = @" +XX +XX{ +XX abc = def +XX ghi = jkl +XX INNER_NODE +XX { +XX mno = pqr +XX } +XX} +".TrimStart().Replace("\r", null); + + StringBuilder sb = new StringBuilder(); + node.PrettyPrint(ref sb, "XX"); + Assert.Equal(expected, sb.ToString()); + } } } From b52a80ea42e54cef13a90290c84eaeb493823649 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 19 Sep 2017 21:12:26 -0700 Subject: [PATCH 105/342] Get rid of unnecesary using directives --- ModuleManager/Extensions/UrlConfigExtensions.cs | 3 --- ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs | 3 --- 2 files changed, 6 deletions(-) diff --git a/ModuleManager/Extensions/UrlConfigExtensions.cs b/ModuleManager/Extensions/UrlConfigExtensions.cs index a67ab150..48cab492 100644 --- a/ModuleManager/Extensions/UrlConfigExtensions.cs +++ b/ModuleManager/Extensions/UrlConfigExtensions.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace ModuleManager.Extensions { diff --git a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs index 23ab20d2..43674e0e 100644 --- a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Xunit; using TestUtils; using ModuleManager.Extensions; From 45a4c0c2222159a75c17415c16ef499c9e785c71 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 19 Sep 2017 23:56:45 -0700 Subject: [PATCH 106/342] Don't run PrePatchInit if cache is being used Mod list is not necessary --- ModuleManager/MMPatchLoader.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index fc469574..a0505ba0 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -249,16 +249,14 @@ private IEnumerator ProcessPatch() #if DEBUG //useCache = false; #endif - - status = "Pre patch init"; - logger.Info(status); yield return null; - PrePatchInit(); - - if (!useCache) { + status = "Pre patch init"; + logger.Info(status); + PrePatchInit(); + yield return null; // If we don't use the cache then it is best to clean the PartDatabase.cfg From d397a80a1157dbd74374fc9abd8a4f9e4b1ebbb3 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 20 Sep 2017 00:02:46 -0700 Subject: [PATCH 107/342] Eliminate mods instance variable --- ModuleManager/MMPatchLoader.cs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index a0505ba0..5375c7d5 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -23,9 +23,6 @@ namespace ModuleManager [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public class MMPatchLoader : LoadingSystem { - - private List mods; - public string status = ""; public string errors = ""; @@ -137,7 +134,7 @@ public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) if (!postPatchCallbacks.Contains(callback)) postPatchCallbacks.Add(callback); } - private void PrePatchInit() + private IEnumerable GenerateModList() { #region List of mods @@ -150,7 +147,7 @@ private void PrePatchInit() // //log(envInfo); - mods = new List(); + List mods = new List(); string modlist = "compiling list of loaded mods...\nMod DLLs found:\n"; @@ -228,6 +225,8 @@ private void PrePatchInit() mods.Sort(); #endregion List of mods + + return mods; } private IEnumerator ProcessPatch() @@ -255,7 +254,7 @@ private IEnumerator ProcessPatch() { status = "Pre patch init"; logger.Info(status); - PrePatchInit(); + IEnumerable mods = GenerateModList(); yield return null; @@ -273,7 +272,7 @@ private IEnumerator ProcessPatch() status = "Checking NEEDS."; logger.Info(status); yield return null; - CheckNeeds(); + CheckNeeds(mods); #endregion Check Needs @@ -781,7 +780,7 @@ private void StatusUpdate() #region Needs checking - private void CheckNeeds() + private void CheckNeeds(IEnumerable mods) { UrlDir.UrlConfig[] allConfigs = GameDatabase.Instance.root.AllConfigs.ToArray(); @@ -802,7 +801,7 @@ private void CheckNeeds() mod.parent.configs.Remove(currentMod); string type = currentMod.type; - if (!CheckNeeds(ref type)) + if (!CheckNeeds(ref type, mods)) { progress.NeedsUnsatisfiedRoot(currentMod); continue; @@ -815,7 +814,8 @@ private void CheckNeeds() } // Recursively check the contents - CheckNeeds(new NodeStack(mod.config), new PatchContext(mod, GameDatabase.Instance.root, logger, progress)); + PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); + CheckNeeds(new NodeStack(mod.config), context, mods); } catch (Exception ex) { @@ -825,7 +825,7 @@ private void CheckNeeds() } } - private void CheckNeeds(NodeStack stack, PatchContext context) + private void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods) { bool needsCopy = false; ConfigNode original = stack.value; @@ -836,7 +836,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context) string valname = val.name; try { - if (CheckNeeds(ref valname)) + if (CheckNeeds(ref valname, mods)) { copy.AddValue(valname, val.value); } @@ -871,10 +871,10 @@ private void CheckNeeds(NodeStack stack, PatchContext context) try { - if (CheckNeeds(ref nodeName)) + if (CheckNeeds(ref nodeName, mods)) { node.name = nodeName; - CheckNeeds(stack.Push(node), context); + CheckNeeds(stack.Push(node), context, mods); copy.AddNode(node); } else @@ -902,7 +902,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context) /// /// Returns true if needs are satisfied. /// - private bool CheckNeeds(ref string name) + private bool CheckNeeds(ref string name, IEnumerable mods) { if (name == null) return true; From 12780a636b8ace12511cfd71d8ec9302b3a15201 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 20 Sep 2017 00:17:17 -0700 Subject: [PATCH 108/342] Use method param rather than instance var Makes things easier to disentagle --- ModuleManager/MMPatchLoader.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 5375c7d5..4a7b26b9 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -848,12 +848,12 @@ private void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable Date: Wed, 20 Sep 2017 00:21:56 -0700 Subject: [PATCH 109/342] Eliminate Update Status will be updated when necessary anyway --- ModuleManager/MMPatchLoader.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 4a7b26b9..e066cba8 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -123,12 +123,6 @@ public override void StartLoad() StartCoroutine(ProcessPatch()); } - public void Update() - { - if (progress.AppliedPatchCount > 0 && HighLogic.LoadedScene == GameScenes.LOADING) - StatusUpdate(); - } - public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) { if (!postPatchCallbacks.Contains(callback)) From 7a40730ad2b966bcc0eccee8968af93d950efaa7 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 20 Sep 2017 00:26:28 -0700 Subject: [PATCH 110/342] Eliminate redundant logging MMPatchLoader logs this info itself --- ModuleManager/ModuleManager.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 856d4af2..bd6867ac 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -297,10 +297,6 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) while (!MMPatchLoader.Instance.IsReady()) yield return null; - Log("DB Reload OK with patchCount=" + MMPatchLoader.Instance.progress.PatchedNodeCount + " errorCount=" + - MMPatchLoader.Instance.progress.ErrorCount + " needsUnsatisfiedCount=" + - MMPatchLoader.Instance.progress.NeedsUnsatisfiedCount + " exceptionCount=" + MMPatchLoader.Instance.progress.ExceptionCount); - PartResourceLibrary.Instance.LoadDefinitions(); PartUpgradeManager.Handler.FillUpgrades(); From 7e714a47ba7f158718e878ffcad83324f5b30453 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 20 Sep 2017 00:35:00 -0700 Subject: [PATCH 111/342] Keep track of progress fraction independently --- ModuleManager/MMPatchLoader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index e066cba8..67e0cca5 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -62,6 +62,7 @@ public class MMPatchLoader : LoadingSystem private IBasicLogger logger; public IPatchProgress progress; + private float progressFraction = 0; public static MMPatchLoader Instance { get; private set; } @@ -101,7 +102,7 @@ public override bool IsReady() return ready; } - public override float ProgressFraction() => progress.ProgressFraction; + public override float ProgressFraction() => progressFraction; public override string ProgressTitle() { @@ -758,12 +759,15 @@ private void LoadCache() logger.Warning("Parent null for " + parentUrl); } } + progressFraction = 1; logger.Info("Cache Loaded"); } private void StatusUpdate() { status = "ModuleManager: " + progress.PatchedNodeCount + " patch" + (progress.PatchedNodeCount != 1 ? "es" : "") + (useCache ? " loaded from cache" : " applied"); + progressFraction = progress.ProgressFraction; + if (progress.ErrorCount > 0) status += ", found " + progress.ErrorCount + " error" + (progress.ErrorCount != 1 ? "s" : "") + ""; From 9236be107824be90bb56840dc20486aacb4fce7d Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 20 Sep 2017 00:35:38 -0700 Subject: [PATCH 112/342] Make StatusUpdate less general If cache is used, status only needs to be set once, no need to check it every time --- ModuleManager/MMPatchLoader.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 67e0cca5..c94769fd 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -354,8 +354,6 @@ private IEnumerator ProcessPatch() LoadCache(); } - StatusUpdate(); - logger.Info(status + "\n" + errors); #if DEBUG @@ -735,7 +733,7 @@ private void LoadCache() ConfigNode cache = ConfigNode.Load(cachePath); if (cache.HasValue("patchedNodeCount") && int.TryParse(cache.GetValue("patchedNodeCount"), out int patchedNodeCount)) - progress.PatchedNodeCount = patchedNodeCount; + status = "ModuleManager: " + patchedNodeCount + " patch" + (patchedNodeCount != 1 ? "es" : "") + " loaded from cache"; // Create the fake file where we load the physic config cache UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); @@ -765,7 +763,7 @@ private void LoadCache() private void StatusUpdate() { - status = "ModuleManager: " + progress.PatchedNodeCount + " patch" + (progress.PatchedNodeCount != 1 ? "es" : "") + (useCache ? " loaded from cache" : " applied"); + status = "ModuleManager: " + progress.PatchedNodeCount + " patch" + (progress.PatchedNodeCount != 1 ? "es" : "") + " applied"; progressFraction = progress.ProgressFraction; From 8696143e2f3fd17dab5f92f81798141d4eeff69f Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 20 Sep 2017 00:41:47 -0700 Subject: [PATCH 113/342] Move this What I get for trying to make a bunch of changes and then split them into small commits --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index c94769fd..0fc1fbfb 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -763,9 +763,9 @@ private void LoadCache() private void StatusUpdate() { - status = "ModuleManager: " + progress.PatchedNodeCount + " patch" + (progress.PatchedNodeCount != 1 ? "es" : "") + " applied"; progressFraction = progress.ProgressFraction; + status = "ModuleManager: " + progress.PatchedNodeCount + " patch" + (progress.PatchedNodeCount != 1 ? "es" : "") + " applied"; if (progress.ErrorCount > 0) status += ", found " + progress.ErrorCount + " error" + (progress.ErrorCount != 1 ? "s" : "") + ""; From 58d52d9572fce69dc234dbd7d1ba4f5b78e48313 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 20 Sep 2017 00:44:04 -0700 Subject: [PATCH 114/342] Eliminate Progress instance variable Make it local, inject where needed --- ModuleManager/MMPatchLoader.cs | 41 ++++++++++++++++------------------ 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 0fc1fbfb..0c00bf64 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -61,7 +61,6 @@ public class MMPatchLoader : LoadingSystem private IBasicLogger logger; - public IPatchProgress progress; private float progressFraction = 0; public static MMPatchLoader Instance { get; private set; } @@ -86,7 +85,6 @@ private void Awake() shaPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "GameData" + Path.DirectorySeparatorChar + "ModuleManager.ConfigSHA"; logger = new ModLogger("ModuleManager", Debug.logger); - progress = new PatchProgress(logger); } private bool ready; @@ -114,8 +112,6 @@ public override void StartLoad() patchSw.Reset(); patchSw.Start(); - progress = new PatchProgress(logger); - ready = false; // DB check used to track the now fixed TextureReplacer corruption @@ -129,7 +125,7 @@ public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) if (!postPatchCallbacks.Contains(callback)) postPatchCallbacks.Add(callback); } - private IEnumerable GenerateModList() + private IEnumerable GenerateModList(IPatchProgress progress) { #region List of mods @@ -247,9 +243,10 @@ private IEnumerator ProcessPatch() if (!useCache) { + IPatchProgress progress = new PatchProgress(logger); status = "Pre patch init"; logger.Info(status); - IEnumerable mods = GenerateModList(); + IEnumerable mods = GenerateModList(progress); yield return null; @@ -267,7 +264,7 @@ private IEnumerator ProcessPatch() status = "Checking NEEDS."; logger.Info(status); yield return null; - CheckNeeds(mods); + CheckNeeds(mods, progress); #endregion Check Needs @@ -290,21 +287,21 @@ private IEnumerator ProcessPatch() yield return null; // :First node - yield return StartCoroutine(ApplyPatch(":FIRST", patchList.firstPatches)); + yield return StartCoroutine(ApplyPatch(":FIRST", patchList.firstPatches, progress)); // any node without a :pass - yield return StartCoroutine(ApplyPatch(":LEGACY (default)", patchList.legacyPatches)); + yield return StartCoroutine(ApplyPatch(":LEGACY (default)", patchList.legacyPatches, progress)); foreach (PatchList.ModPass pass in patchList.modPasses) { string upperModName = pass.name.ToUpper(); - yield return StartCoroutine(ApplyPatch(":BEFORE[" + upperModName + "]", pass.beforePatches)); - yield return StartCoroutine(ApplyPatch(":FOR[" + upperModName + "]", pass.forPatches)); - yield return StartCoroutine(ApplyPatch(":AFTER[" + upperModName + "]", pass.afterPatches)); + yield return StartCoroutine(ApplyPatch(":BEFORE[" + upperModName + "]", pass.beforePatches, progress)); + yield return StartCoroutine(ApplyPatch(":FOR[" + upperModName + "]", pass.forPatches, progress)); + yield return StartCoroutine(ApplyPatch(":AFTER[" + upperModName + "]", pass.afterPatches, progress)); } // :Final node - yield return StartCoroutine(ApplyPatch(":FINAL", patchList.finalPatches)); + yield return StartCoroutine(ApplyPatch(":FINAL", patchList.finalPatches, progress)); PurgeUnused(); @@ -338,7 +335,7 @@ private IEnumerator ProcessPatch() status = "Saving Cache"; logger.Info(status); yield return null; - CreateCache(); + CreateCache(progress.PatchedNodeCount); } #endregion Saving Cache @@ -630,7 +627,7 @@ private ConfigNode GetFileNode(ConfigNode shaConfigNode, string filename) } - private void CreateCache() + private void CreateCache(int patchedNodeCount) { ConfigNode shaConfigNode = new ConfigNode(); shaConfigNode.AddValue("SHA", configSha); @@ -640,7 +637,7 @@ private void CreateCache() ConfigNode cache = new ConfigNode(); - cache.AddValue("patchedNodeCount", progress.PatchedNodeCount.ToString()); + cache.AddValue("patchedNodeCount", patchedNodeCount.ToString()); foreach (UrlDir.UrlConfig config in GameDatabase.Instance.root.AllConfigs) { @@ -761,7 +758,7 @@ private void LoadCache() logger.Info("Cache Loaded"); } - private void StatusUpdate() + private void StatusUpdate(IPatchProgress progress) { progressFraction = progress.ProgressFraction; @@ -776,7 +773,7 @@ private void StatusUpdate() #region Needs checking - private void CheckNeeds(IEnumerable mods) + private void CheckNeeds(IEnumerable mods, IPatchProgress progress) { UrlDir.UrlConfig[] allConfigs = GameDatabase.Instance.root.AllConfigs.ToArray(); @@ -953,9 +950,9 @@ private void PurgeUnused() #region Applying Patches // Apply patch to all relevent nodes - public IEnumerator ApplyPatch(string Stage, IEnumerable patches) + public IEnumerator ApplyPatch(string Stage, IEnumerable patches, IPatchProgress progress) { - StatusUpdate(); + StatusUpdate(progress); logger.Info(Stage + " pass"); yield return null; @@ -1059,11 +1056,11 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche if (nextYield < Time.realtimeSinceStartup) { nextYield = Time.realtimeSinceStartup + yieldInterval; - StatusUpdate(); + StatusUpdate(progress); yield return null; } } - StatusUpdate(); + StatusUpdate(progress); yield return null; } From 2645d222082cf9a5913590ac97ad6253c6ec92a3 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 20 Sep 2017 00:57:06 -0700 Subject: [PATCH 115/342] Make more methods static All their instance variable dependencies have been eliminated --- ModuleManager/MMPatchLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 0c00bf64..1ff5bc52 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -818,7 +818,7 @@ private void CheckNeeds(IEnumerable mods, IPatchProgress progress) } } - private void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods) + private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods) { bool needsCopy = false; ConfigNode original = stack.value; @@ -895,7 +895,7 @@ private void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable /// Returns true if needs are satisfied. /// - private bool CheckNeeds(ref string name, IEnumerable mods) + private static bool CheckNeeds(ref string name, IEnumerable mods) { if (name == null) return true; From 824b077dfc46eee2998bbc56ea3fb28f7c96b776 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 23 Sep 2017 11:59:29 -0700 Subject: [PATCH 116/342] This is no longer necessary And will probably result in an error anyway --- ModuleManager/MMPatchLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 1ff5bc52..3008284f 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1051,7 +1051,6 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche { progress.Exception(mod, "Exception while processing node : " + mod.SafeUrl(), e); logger.Error("Processed node was\n" + PrettyConfig(mod)); - mod.parent.configs.Remove(mod); } if (nextYield < Time.realtimeSinceStartup) { From ac0a149a21efe830c2c8e7df2468526395b6fe6d Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 23 Sep 2017 12:00:50 -0700 Subject: [PATCH 117/342] Move exception handling outside of PrettyConfig Callers really shouldn't be trying to print the result if it resulted in an exception anyway --- ModuleManager/MMPatchLoader.cs | 39 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 3008284f..74edc282 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -813,7 +813,15 @@ private void CheckNeeds(IEnumerable mods, IPatchProgress progress) catch (Exception ex) { progress.Exception(currentMod, "Exception while checking needs : " + currentMod.SafeUrl() + " with a type of " + currentMod.type, ex); - logger.Error("Node is : " + PrettyConfig(currentMod)); + + try + { + logger.Error("Node is : " + PrettyConfig(currentMod)); + } + catch(Exception ex2) + { + logger.Exception("Exception while attempting to print a node", ex2); + } } } } @@ -1050,7 +1058,15 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche catch (Exception e) { progress.Exception(mod, "Exception while processing node : " + mod.SafeUrl(), e); - logger.Error("Processed node was\n" + PrettyConfig(mod)); + + try + { + logger.Error("Processed node was\n" + PrettyConfig(mod)); + } + catch (Exception ex2) + { + logger.Exception("Exception while attempting to print a node", ex2); + } } if (nextYield < Time.realtimeSinceStartup) { @@ -2229,26 +2245,19 @@ private static void InsertValue(ConfigNode newNode, int index, string name, stri newNode.AddValue(name, value); } - private string PrettyConfig(UrlDir.UrlConfig config) + private static string PrettyConfig(UrlDir.UrlConfig config) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("{0}[{1}]\n", config.type ?? "NULL", config.name ?? "NULL"); - try + if (config.config != null) { - if (config.config != null) - { - config.config.PrettyPrint(ref sb, " "); - } - else - { - sb.Append("NULL\n"); - } - sb.Append("\n"); + config.config.PrettyPrint(ref sb, " "); } - catch (Exception e) + else { - logger.Exception("PrettyConfig Exception", e); + sb.Append("NULL\n"); } + sb.Append("\n"); return sb.ToString(); } From eb18b601a5662c74712af4eea74c0c60cb7ca467 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 23 Sep 2017 12:18:43 -0700 Subject: [PATCH 118/342] Tweak test This isn't the case it was trying to test --- ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index e63e54a2..106976bd 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -214,7 +214,7 @@ public void TestPrettyPrint__NullStringBuilder() [Fact] public void TestPrettyPrint__NullIndent() { - ConfigNode node = new TestConfigNode() + ConfigNode node = new TestConfigNode("SOME_NODE") { { "abc", "def" }, { "ghi", "jkl" }, @@ -224,10 +224,8 @@ public void TestPrettyPrint__NullIndent() }, }; - node.name = null; - string expected = @" - +SOME_NODE { abc = def ghi = jkl From 4fdfb8911e4b93c8f7c332e8fab077a8a1bc6861 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 23 Sep 2017 20:36:23 -0700 Subject: [PATCH 119/342] Allow adding a ConfigNode.Value in initializer Not useful yet but maybe at some point --- TestUtils/TestConfigNode.cs | 1 + TestUtilsTests/TestConfigNodeTest.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/TestUtils/TestConfigNode.cs b/TestUtils/TestConfigNode.cs index e7612955..a954c107 100644 --- a/TestUtils/TestConfigNode.cs +++ b/TestUtils/TestConfigNode.cs @@ -9,6 +9,7 @@ public TestConfigNode() : base() { } public TestConfigNode(string name) : base(name) { } public void Add(string name, string value) => AddValue(name, value); + public void Add(ConfigNode.Value value) => values.Add(value); public void Add(string name, ConfigNode node) => AddNode(name, node); public void Add(ConfigNode node) => AddNode(node); diff --git a/TestUtilsTests/TestConfigNodeTest.cs b/TestUtilsTests/TestConfigNodeTest.cs index 872b4bbb..6b00b4de 100644 --- a/TestUtilsTests/TestConfigNodeTest.cs +++ b/TestUtilsTests/TestConfigNodeTest.cs @@ -15,6 +15,7 @@ public void TestTestConfigNode() { "value2", "something else" }, { "multiple", "first" }, { "multiple", "second" }, + new ConfigNode.Value("foo", "bar"), { "NODE_1", new TestConfigNode { { "name", "something" }, @@ -36,6 +37,7 @@ public void TestTestConfigNode() Assert.Equal("something", node.GetValue("value1")); Assert.Equal("something else", node.GetValue("value2")); Assert.Equal(new[] { "first", "second" }, node.GetValues("multiple")); + Assert.Equal("bar", node.GetValue("foo")); ConfigNode innerNode1 = node.GetNode("NODE_1"); Assert.NotNull(innerNode1); From 70eca817c4d6d398c471b84d6ac0288ad4128e37 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 23 Sep 2017 21:31:22 -0700 Subject: [PATCH 120/342] Extract PrettyConfig (for UrlConfig) --- .../Extensions/ConfigNodeExtensions.cs | 2 +- .../Extensions/UrlConfigExtensions.cs | 19 +++ ModuleManager/MMPatchLoader.cs | 20 +-- .../Extensions/ConfigNodeExtensionsTest.cs | 2 +- .../Extensions/UrlConfigExtensionsTest.cs | 146 ++++++++++++++++++ 5 files changed, 169 insertions(+), 20 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 55d900f0..6940048a 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -34,7 +34,7 @@ public static void PrettyPrint(this ConfigNode node, ref StringBuilder sb, strin if (indent == null) indent = string.Empty; if (node == null) { - sb.Append(indent + ""); + sb.Append(indent + "\n"); return; } sb.AppendFormat("{0}{1}\n{2}{{\n", indent, node.name ?? "", indent); diff --git a/ModuleManager/Extensions/UrlConfigExtensions.cs b/ModuleManager/Extensions/UrlConfigExtensions.cs index 48cab492..86001c39 100644 --- a/ModuleManager/Extensions/UrlConfigExtensions.cs +++ b/ModuleManager/Extensions/UrlConfigExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Text; namespace ModuleManager.Extensions { @@ -42,5 +43,23 @@ public static string SafeUrl(this UrlDir.UrlConfig url) else return parentUrl + "/" + nodeName; } + + public static string PrettyPrint(this UrlDir.UrlConfig config) + { + if (config == null) return ""; + + StringBuilder sb = new StringBuilder(); + + if (config.type == null) sb.Append(""); + else sb.Append(config.type); + + if (config.name == null) sb.Append("[]"); + else if (config.name != config.type) sb.AppendFormat("[{0}]", config.name); + + sb.Append('\n'); + config.config.PrettyPrint(ref sb, " "); + + return sb.ToString(); + } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 74edc282..0253f5ef 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -816,7 +816,7 @@ private void CheckNeeds(IEnumerable mods, IPatchProgress progress) try { - logger.Error("Node is : " + PrettyConfig(currentMod)); + logger.Error("Node is : " + currentMod.PrettyPrint()); } catch(Exception ex2) { @@ -1061,7 +1061,7 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche try { - logger.Error("Processed node was\n" + PrettyConfig(mod)); + logger.Error("Processed node was\n" + mod.PrettyPrint()); } catch (Exception ex2) { @@ -2245,22 +2245,6 @@ private static void InsertValue(ConfigNode newNode, int index, string name, stri newNode.AddValue(name, value); } - private static string PrettyConfig(UrlDir.UrlConfig config) - { - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("{0}[{1}]\n", config.type ?? "NULL", config.name ?? "NULL"); - if (config.config != null) - { - config.config.PrettyPrint(ref sb, " "); - } - else - { - sb.Append("NULL\n"); - } - sb.Append("\n"); - return sb.ToString(); - } - //FindConfigNodeIn finds and returns a ConfigNode in src of type nodeType. //If nodeName is not null, it will only find a node of type nodeType with the value name=nodeName. //If nodeTag is not null, it will only find a node of type nodeType with the value name=nodeName and tag=nodeTag. diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index 106976bd..e82971af 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -197,7 +197,7 @@ public void TestPrettyPrint__NullNode() ConfigNode node = null; StringBuilder sb = new StringBuilder(); node.PrettyPrint(ref sb, "XX"); - Assert.Equal("XX", sb.ToString()); + Assert.Equal("XX\n", sb.ToString()); } [Fact] diff --git a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs index 43674e0e..14037714 100644 --- a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs @@ -72,5 +72,151 @@ public void TestSafeUrl__BlankName() UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def", node); Assert.Equal("abc/def/", url.SafeUrl()); } + + [Fact] + public void TestPrettyPrint() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE_1") + { + { "mno", "pqr" }, + }, + }; + + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", node); + + string expected = @" +SOME_NODE + SOME_NODE + { + abc = def + ghi = jkl + INNER_NODE_1 + { + mno = pqr + } + } +".TrimStart().Replace("\r", null); + + Assert.Equal(expected, url.PrettyPrint()); + } + + [Fact] + public void TestPrettyPrint__NameValue() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "name", "Inigo Montoya" }, + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE_1") + { + { "mno", "pqr" }, + }, + }; + + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", node); + + string expected = @" +SOME_NODE[Inigo Montoya] + SOME_NODE + { + name = Inigo Montoya + abc = def + ghi = jkl + INNER_NODE_1 + { + mno = pqr + } + } +".TrimStart().Replace("\r", null); + + Assert.Equal(expected, url.PrettyPrint()); + } + + [Fact] + public void TestPrettyPrint__NullName() + { + ConfigNode node = new TestConfigNode() + { + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE_1") + { + { "mno", "pqr" }, + }, + }; + + node.name = null; + + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", node); + + string expected = @" +[] + + { + abc = def + ghi = jkl + INNER_NODE_1 + { + mno = pqr + } + } +".TrimStart().Replace("\r", null); + + Assert.Equal(expected, url.PrettyPrint()); + } + + [Fact] + public void TestPrettyPrint__NullNameValue() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "name", "temp" }, + { "abc", "def" }, + { "ghi", "jkl" }, + new TestConfigNode("INNER_NODE_1") + { + { "mno", "pqr" }, + }, + }; + + node.values[0].value = null; + + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", node); + + string expected = @" +SOME_NODE[] + SOME_NODE + { + name = + abc = def + ghi = jkl + INNER_NODE_1 + { + mno = pqr + } + } +".TrimStart().Replace("\r", null); + + Assert.Equal(expected, url.PrettyPrint()); + } + + [Fact] + public void TestPrettyPrint__NullNode() + { + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", new ConfigNode("SOME_NODE")); + url.config = null; + + string expected = @" +SOME_NODE + +".TrimStart().Replace("\r", null); + + Assert.Equal(expected, url.PrettyPrint()); + } } } From 55308af509bf7b8c082c641528df24847a5b5982 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 23 Sep 2017 22:43:56 -0700 Subject: [PATCH 121/342] Add one more test --- ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs index 14037714..d9ca1c49 100644 --- a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs @@ -218,5 +218,12 @@ public void TestPrettyPrint__NullNode() Assert.Equal(expected, url.PrettyPrint()); } + + [Fact] + public void TestPrettyPrint__Null() + { + UrlDir.UrlConfig url = null; + Assert.Equal("", url.PrettyPrint()); + } } } From 1a7d1e36c64d27318b002a5505f5959be21551d1 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 23 Sep 2017 23:28:28 -0700 Subject: [PATCH 122/342] Make CheckNeeds static Can now be extracted --- ModuleManager/MMPatchLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 0253f5ef..e78e6693 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -264,7 +264,7 @@ private IEnumerator ProcessPatch() status = "Checking NEEDS."; logger.Info(status); yield return null; - CheckNeeds(mods, progress); + CheckNeeds(mods, progress, logger); #endregion Check Needs @@ -773,7 +773,7 @@ private void StatusUpdate(IPatchProgress progress) #region Needs checking - private void CheckNeeds(IEnumerable mods, IPatchProgress progress) + private static void CheckNeeds(IEnumerable mods, IPatchProgress progress, IBasicLogger logger) { UrlDir.UrlConfig[] allConfigs = GameDatabase.Instance.root.AllConfigs.ToArray(); From bc451c5bdf2dafa43972a7930ee9483a5366e861 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 23 Sep 2017 23:28:50 -0700 Subject: [PATCH 123/342] This can already be static --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index e78e6693..0043495a 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -942,7 +942,7 @@ private static bool CheckNeeds(ref string name, IEnumerable mods) return true; } - private void PurgeUnused() + private static void PurgeUnused() { foreach (UrlDir.UrlConfig mod in GameDatabase.Instance.root.AllConfigs.ToArray()) { From 60cb30aa9e47a292bb08be23f46bfd8d8caaccc5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 27 Sep 2017 23:10:14 -0700 Subject: [PATCH 124/342] Remove unnecessary Using --- ModuleManager/Extensions/NodeStackExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManager/Extensions/NodeStackExtensions.cs b/ModuleManager/Extensions/NodeStackExtensions.cs index 13e81cf7..c6357dca 100644 --- a/ModuleManager/Extensions/NodeStackExtensions.cs +++ b/ModuleManager/Extensions/NodeStackExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; using NodeStack = ModuleManager.Collections.ImmutableStack; From a035b672edecdefd73f22fefef00c56a9270360f Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 27 Sep 2017 23:13:36 -0700 Subject: [PATCH 125/342] Improve url and node printing * Handle null name explicitly * Include url when printing a UrlConfig --- .../Extensions/UrlConfigExtensions.cs | 11 ++----- .../Extensions/UrlConfigExtensionsTest.cs | 32 +++++++++---------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/ModuleManager/Extensions/UrlConfigExtensions.cs b/ModuleManager/Extensions/UrlConfigExtensions.cs index 86001c39..d5ce9fbe 100644 --- a/ModuleManager/Extensions/UrlConfigExtensions.cs +++ b/ModuleManager/Extensions/UrlConfigExtensions.cs @@ -15,9 +15,9 @@ public static string SafeUrl(this UrlDir.UrlConfig url) { nodeName = url.type; } - else if (!string.IsNullOrEmpty(url.config?.name?.Trim())) + else if (url.type == null) { - nodeName = url.config.name; + nodeName = ""; } else { @@ -50,12 +50,7 @@ public static string PrettyPrint(this UrlDir.UrlConfig config) StringBuilder sb = new StringBuilder(); - if (config.type == null) sb.Append(""); - else sb.Append(config.type); - - if (config.name == null) sb.Append("[]"); - else if (config.name != config.type) sb.AppendFormat("[{0}]", config.name); - + sb.Append(config.SafeUrl()); sb.Append('\n'); config.config.PrettyPrint(ref sb, " "); diff --git a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs index d9ca1c49..953edfe9 100644 --- a/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/UrlConfigExtensionsTest.cs @@ -40,7 +40,7 @@ public void TestSafeUrl__NullParent__NullName() name = null }; UrlDir.UrlConfig url = new UrlDir.UrlConfig(null, node); - Assert.Equal("", url.SafeUrl()); + Assert.Equal("", url.SafeUrl()); } [Fact] @@ -59,7 +59,7 @@ public void TestSafeUrl__NullName() }; node.name = null; UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def", node); - Assert.Equal("abc/def/", url.SafeUrl()); + Assert.Equal("abc/def/", url.SafeUrl()); } [Fact] @@ -89,7 +89,7 @@ public void TestPrettyPrint() UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", node); string expected = @" -SOME_NODE +abc/def/SOME_NODE SOME_NODE { abc = def @@ -105,11 +105,10 @@ public void TestPrettyPrint() } [Fact] - public void TestPrettyPrint__NameValue() + public void TestPrettyPrint__NullName() { - ConfigNode node = new TestConfigNode("SOME_NODE") + ConfigNode node = new TestConfigNode() { - { "name", "Inigo Montoya" }, { "abc", "def" }, { "ghi", "jkl" }, new TestConfigNode("INNER_NODE_1") @@ -118,13 +117,14 @@ public void TestPrettyPrint__NameValue() }, }; + node.name = null; + UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", node); string expected = @" -SOME_NODE[Inigo Montoya] - SOME_NODE +abc/def/ + { - name = Inigo Montoya abc = def ghi = jkl INNER_NODE_1 @@ -138,9 +138,9 @@ SOME_NODE[Inigo Montoya] } [Fact] - public void TestPrettyPrint__NullName() + public void TestPrettyPrint__BlankName() { - ConfigNode node = new TestConfigNode() + ConfigNode node = new TestConfigNode(" ") { { "abc", "def" }, { "ghi", "jkl" }, @@ -150,13 +150,11 @@ public void TestPrettyPrint__NullName() }, }; - node.name = null; - UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", node); string expected = @" -[] - +abc/def/ + { abc = def ghi = jkl @@ -189,7 +187,7 @@ public void TestPrettyPrint__NullNameValue() UrlDir.UrlConfig url = UrlBuilder.CreateConfig("abc/def.cfg", node); string expected = @" -SOME_NODE[] +abc/def/SOME_NODE SOME_NODE { name = @@ -212,7 +210,7 @@ public void TestPrettyPrint__NullNode() url.config = null; string expected = @" -SOME_NODE +abc/def/SOME_NODE ".TrimStart().Replace("\r", null); From 59a922067956db3ba32b6c248e53714958425296 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 27 Sep 2017 23:15:11 -0700 Subject: [PATCH 126/342] Extract CheckNeeds Equality vs sameness mostly not tested for now, need to determine desired behavior --- ModuleManager/MMPatchLoader.cs | 175 +---------- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/NeedsChecker.cs | 176 +++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/NeedsCheckerTest.cs | 310 +++++++++++++++++++ 5 files changed, 489 insertions(+), 174 deletions(-) create mode 100644 ModuleManager/NeedsChecker.cs create mode 100644 ModuleManagerTests/NeedsCheckerTest.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 0043495a..ac8bd6bb 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -264,7 +264,7 @@ private IEnumerator ProcessPatch() status = "Checking NEEDS."; logger.Info(status); yield return null; - CheckNeeds(mods, progress, logger); + NeedsChecker.CheckNeeds(GameDatabase.Instance.root, mods, progress, logger); #endregion Check Needs @@ -771,177 +771,6 @@ private void StatusUpdate(IPatchProgress progress) status += ", encountered " + progress.ExceptionCount + " exception" + (progress.ExceptionCount != 1 ? "s" : "") + ""; } - #region Needs checking - - private static void CheckNeeds(IEnumerable mods, IPatchProgress progress, IBasicLogger logger) - { - UrlDir.UrlConfig[] allConfigs = GameDatabase.Instance.root.AllConfigs.ToArray(); - - // Check the NEEDS parts first. - foreach (UrlDir.UrlConfig mod in allConfigs) - { - UrlDir.UrlConfig currentMod = mod; - try - { - if (mod.config.name == null) - { - progress.Error(currentMod, "Error - Node in file " + currentMod.parent.url + " subnode: " + currentMod.type + - " has config.name == null"); - } - - if (currentMod.type.Contains(":NEEDS[")) - { - mod.parent.configs.Remove(currentMod); - string type = currentMod.type; - - if (!CheckNeeds(ref type, mods)) - { - progress.NeedsUnsatisfiedRoot(currentMod); - continue; - } - - ConfigNode copy = new ConfigNode(type); - copy.ShallowCopyFrom(currentMod.config); - currentMod = new UrlDir.UrlConfig(currentMod.parent, copy); - mod.parent.configs.Add(currentMod); - } - - // Recursively check the contents - PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); - CheckNeeds(new NodeStack(mod.config), context, mods); - } - catch (Exception ex) - { - progress.Exception(currentMod, "Exception while checking needs : " + currentMod.SafeUrl() + " with a type of " + currentMod.type, ex); - - try - { - logger.Error("Node is : " + currentMod.PrettyPrint()); - } - catch(Exception ex2) - { - logger.Exception("Exception while attempting to print a node", ex2); - } - } - } - } - - private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods) - { - bool needsCopy = false; - ConfigNode original = stack.value; - ConfigNode copy = new ConfigNode(original.name); - for (int i = 0; i < original.values.Count; ++i) - { - ConfigNode.Value val = original.values[i]; - string valname = val.name; - try - { - if (CheckNeeds(ref valname, mods)) - { - copy.AddValue(valname, val.value); - } - else - { - needsCopy = true; - context.progress.NeedsUnsatisfiedValue(context.patchUrl, stack, val.name); - } - } - catch (ArgumentOutOfRangeException e) - { - context.progress.Exception("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"", e); - throw; - } - catch (Exception e) - { - context.progress.Exception("General Exception in CheckNeeds for value \"" + val.name + "\"", e); - throw; - } - } - - for (int i = 0; i < original.nodes.Count; ++i) - { - ConfigNode node = original.nodes[i]; - string nodeName = node.name; - - if (nodeName == null) - { - context.progress.Error(context.patchUrl, "Error - Node in file " + context.patchUrl.SafeUrl() + " subnode: " + stack.GetPath() + - " has config.name == null"); - } - - try - { - if (CheckNeeds(ref nodeName, mods)) - { - node.name = nodeName; - CheckNeeds(stack.Push(node), context, mods); - copy.AddNode(node); - } - else - { - needsCopy = true; - context.progress.NeedsUnsatisfiedNode(context.patchUrl, stack.Push(node)); - } - } - catch (ArgumentOutOfRangeException e) - { - context.progress.Exception("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"", e); - throw; - } - catch (Exception e) - { - context.progress.Exception("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"", e); - throw; - } - } - - if (needsCopy) - original.ShallowCopyFrom(copy); - } - - /// - /// Returns true if needs are satisfied. - /// - private static bool CheckNeeds(ref string name, IEnumerable mods) - { - if (name == null) - return true; - - int idxStart = name.IndexOf(":NEEDS["); - if (idxStart < 0) - return true; - int idxEnd = name.IndexOf(']', idxStart + 7); - string needsString = name.Substring(idxStart + 7, idxEnd - idxStart - 7).ToUpper(); - - name = name.Substring(0, idxStart) + name.Substring(idxEnd + 1); - - // Check to see if all the needed dependencies are present. - foreach (string andDependencies in needsString.Split(',', '&')) - { - bool orMatch = false; - foreach (string orDependency in andDependencies.Split('|')) - { - if (orDependency.Length == 0) - continue; - - bool not = orDependency[0] == '!'; - string toFind = not ? orDependency.Substring(1) : orDependency; - bool found = mods.Contains(toFind, StringComparer.OrdinalIgnoreCase); - - if (not == !found) - { - orMatch = true; - break; - } - } - if (!orMatch) - return false; - } - - return true; - } - private static void PurgeUnused() { foreach (UrlDir.UrlConfig mod in GameDatabase.Instance.root.AllConfigs.ToArray()) @@ -953,8 +782,6 @@ private static void PurgeUnused() } } - #endregion Needs checking - #region Applying Patches // Apply patch to all relevent nodes diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 963e432c..6f5cb2cf 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -48,6 +48,7 @@ + diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs new file mode 100644 index 00000000..32d46f3d --- /dev/null +++ b/ModuleManager/NeedsChecker.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using NodeStack = ModuleManager.Collections.ImmutableStack; + +namespace ModuleManager +{ + public static class NeedsChecker + { + public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, IPatchProgress progress, IBasicLogger logger) + { + foreach (UrlDir.UrlConfig mod in gameDatabaseRoot.AllConfigs.ToArray()) + { + UrlDir.UrlConfig currentMod = mod; + try + { + if (mod.config.name == null) + { + progress.Error(currentMod, "Error - Node in file " + currentMod.parent.url + " subnode: " + currentMod.type + + " has config.name == null"); + } + + if (currentMod.type.IndexOf(":NEEDS[", StringComparison.OrdinalIgnoreCase) >= 0) + { + mod.parent.configs.Remove(currentMod); + string type = currentMod.type; + + if (!CheckNeeds(ref type, mods)) + { + progress.NeedsUnsatisfiedRoot(currentMod); + continue; + } + + ConfigNode copy = new ConfigNode(type); + copy.ShallowCopyFrom(currentMod.config); + currentMod = new UrlDir.UrlConfig(currentMod.parent, copy); + mod.parent.configs.Add(currentMod); + } + + // Recursively check the contents + PatchContext context = new PatchContext(mod, gameDatabaseRoot, logger, progress); + CheckNeeds(new NodeStack(mod.config), context, mods); + } + catch (Exception ex) + { + try + { + progress.Exception(currentMod, "Exception while checking needs on root node :\n" + currentMod.PrettyPrint(), ex); + } + catch (Exception ex2) + { + progress.Exception("Exception while attempting to log an exception", ex2); + } + } + } + } + + private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods) + { + bool needsCopy = false; + ConfigNode original = stack.value; + ConfigNode copy = new ConfigNode(original.name); + for (int i = 0; i < original.values.Count; ++i) + { + ConfigNode.Value val = original.values[i]; + string valname = val.name; + try + { + if (CheckNeeds(ref valname, mods)) + { + copy.AddValue(valname, val.value); + } + else + { + needsCopy = true; + context.progress.NeedsUnsatisfiedValue(context.patchUrl, stack, val.name); + } + } + catch (ArgumentOutOfRangeException e) + { + context.progress.Exception("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"", e); + throw; + } + catch (Exception e) + { + context.progress.Exception("General Exception in CheckNeeds for value \"" + val.name + "\"", e); + throw; + } + } + + for (int i = 0; i < original.nodes.Count; ++i) + { + ConfigNode node = original.nodes[i]; + string nodeName = node.name; + + if (nodeName == null) + { + context.progress.Error(context.patchUrl, "Error - Node in file " + context.patchUrl.SafeUrl() + " subnode: " + stack.GetPath() + + " has config.name == null"); + } + + try + { + if (CheckNeeds(ref nodeName, mods)) + { + node.name = nodeName; + CheckNeeds(stack.Push(node), context, mods); + copy.AddNode(node); + } + else + { + needsCopy = true; + context.progress.NeedsUnsatisfiedNode(context.patchUrl, stack.Push(node)); + } + } + catch (ArgumentOutOfRangeException e) + { + context.progress.Exception("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"", e); + throw; + } + catch (Exception e) + { + context.progress.Exception("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"", e); + throw; + } + } + + if (needsCopy) + original.ShallowCopyFrom(copy); + } + + /// + /// Returns true if needs are satisfied. + /// + private static bool CheckNeeds(ref string name, IEnumerable mods) + { + if (name == null) + return true; + + int idxStart = name.IndexOf(":NEEDS[", StringComparison.OrdinalIgnoreCase); + if (idxStart < 0) + return true; + int idxEnd = name.IndexOf(']', idxStart + 7); + string needsString = name.Substring(idxStart + 7, idxEnd - idxStart - 7).ToUpper(); + + name = name.Substring(0, idxStart) + name.Substring(idxEnd + 1); + + // Check to see if all the needed dependencies are present. + foreach (string andDependencies in needsString.Split(',', '&')) + { + bool orMatch = false; + foreach (string orDependency in andDependencies.Split('|')) + { + if (orDependency.Length == 0) + continue; + + bool not = orDependency[0] == '!'; + string toFind = not ? orDependency.Substring(1) : orDependency; + bool found = mods.Contains(toFind, StringComparer.OrdinalIgnoreCase); + + if (not == !found) + { + orMatch = true; + break; + } + } + if (!orMatch) + return false; + } + + return true; + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index dea5c743..95a8566e 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -51,6 +51,7 @@ + diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs new file mode 100644 index 00000000..9a25b0e0 --- /dev/null +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -0,0 +1,310 @@ +using System; +using System.Linq; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; + +namespace ModuleManagerTests +{ + public class NeedsCheckerTest + { + private UrlDir root; + private UrlDir.UrlFile file; + + private IPatchProgress progress; + private IBasicLogger logger; + + public NeedsCheckerTest() + { + root = UrlBuilder.CreateRoot(); + file = UrlBuilder.CreateFile("abc/def.cfg", root); + + progress = Substitute.For(); + logger = Substitute.For(); + } + + [Fact] + public void TestCheckNeeds__Root() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1]"), file); + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:needs[mod1]"), file); + UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod2]:AFTER[mod3]"), file); + + UrlDir.UrlConfig config5 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]"), file); + UrlDir.UrlConfig config6 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:needs[mod3]"), file); + UrlDir.UrlConfig config7 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]:FOR[mod2]"), file); + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); + Assert.Equal(4, configs.Length); + + Assert.Same(config1, configs[0]); + AssertUrlCorrect("SOME_NODE", config2, configs[1]); + AssertUrlCorrect("SOME_NODE", config3, configs[2]); + AssertUrlCorrect("SOME_NODE:AFTER[mod3]", config4, configs[3]); + + progress.Received().NeedsUnsatisfiedRoot(config5); + progress.Received().NeedsUnsatisfiedRoot(config6); + progress.Received().NeedsUnsatisfiedRoot(config7); + } + + [Fact] + public void TestCheckNeeds__Nested() + { + string[] modList = { "mod1", "mod2" }; + + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "aa", "00" }, + { "bb:NEEDS[mod1]", "01" }, + { "cc:NEEDS[mod3]", "02" }, + new TestConfigNode("INNER_NODE_1") + { + { "dd", "03" }, + { "ee", "04" }, + new TestConfigNode("INNER_INNER_NODE_1") + { + { "ff", "05" }, + }, + }, + new TestConfigNode("INNER_NODE_2") + { + { "gg:NEEDS[mod1]", "06" }, + { "hh:NEEDS[mod3]", "07" }, + { "ii", "08" }, + new TestConfigNode("INNER_INNER_NODE_11") + { + { "jj", "09" }, + }, + new TestConfigNode("INNER_INNER_NODE_12:NEEDS[mod2]") + { + { "kk", "10" }, + }, + new TestConfigNode("INNER_INNER_NODE_12:NEEDS[mod3]") + { + { "ll", "11" }, + }, + }, + new TestConfigNode("INNER_NODE_3:NEEDS[mod1]") + { + { "mm:NEEDS[mod1]", "12" }, + { "nn:NEEDS[mod3]", "13" }, + { "oo", "14" }, + new TestConfigNode("INNER_INNER_NODE_21") + { + { "pp", "15" }, + }, + new TestConfigNode("INNER_INNER_NODE_22:NEEDS[mod2]") + { + { "qq", "16" }, + }, + new TestConfigNode("INNER_INNER_NODE_22:NEEDS[mod3]") + { + { "rr", "17" }, + }, + }, + new TestConfigNode("INNER_NODE_4:NEEDS[mod3]") + { + { "ss:NEEDS[mod1]", "18" }, + }, + }; + + UrlDir.UrlConfig origUrl = UrlBuilder.CreateConfig(node, file); + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); + Assert.Equal(1, configs.Length); + + UrlDir.UrlConfig url = configs[0]; + Assert.Equal("SOME_NODE", url.type); + ConfigNode newNode = url.config; + Assert.Equal("SOME_NODE", newNode.name); + + Assert.Equal(2, newNode.values.Count); + Assert.Equal(3, newNode.nodes.Count); + + Assert.Equal("aa", newNode.values[0].name); + Assert.Equal("00", newNode.values[0].value); + + Assert.Equal("bb", newNode.values[1].name); + Assert.Equal("01", newNode.values[1].value); + + Assert.Same(node.nodes[0], newNode.nodes[0]); + Assert.Equal("INNER_NODE_1", newNode.nodes[0].name); + + Assert.Equal(2, newNode.nodes[0].values.Count); + Assert.Equal(1, newNode.nodes[0].nodes.Count); + + Assert.Equal("dd", newNode.nodes[0].values[0].name); + Assert.Equal("03", newNode.nodes[0].values[0].value); + + Assert.Equal("ee", newNode.nodes[0].values[1].name); + Assert.Equal("04", newNode.nodes[0].values[1].value); + + Assert.Equal("INNER_INNER_NODE_1", newNode.nodes[0].nodes[0].name); + + Assert.Equal(1, newNode.nodes[0].nodes[0].values.Count); + Assert.Equal(0, newNode.nodes[0].nodes[0].nodes.Count); + + Assert.Equal("ff", newNode.nodes[0].nodes[0].values[0].name); + Assert.Equal("05", newNode.nodes[0].nodes[0].values[0].value); + + // Assert.NotSame(node.nodes[1], newNode.nodes[1]); + Assert.Equal("INNER_NODE_2", newNode.nodes[1].name); + + Assert.Equal(2, newNode.nodes[1].values.Count); + Assert.Equal(2, newNode.nodes[1].nodes.Count); + + Assert.Equal("gg", newNode.nodes[1].values[0].name); + Assert.Equal("06", newNode.nodes[1].values[0].value); + + Assert.Equal("ii", newNode.nodes[1].values[1].name); + Assert.Equal("08", newNode.nodes[1].values[1].value); + + Assert.Equal("INNER_INNER_NODE_11", newNode.nodes[1].nodes[0].name); + + Assert.Equal("jj", newNode.nodes[1].nodes[0].values[0].name); + Assert.Equal("09", newNode.nodes[1].nodes[0].values[0].value); + + Assert.Equal("INNER_INNER_NODE_12", newNode.nodes[1].nodes[1].name); + + Assert.Equal("kk", newNode.nodes[1].nodes[1].values[0].name); + Assert.Equal("10", newNode.nodes[1].nodes[1].values[0].value); + + // Assert.NotSame(node.nodes[1], newNode.nodes[1]); + Assert.Equal("INNER_NODE_3", newNode.nodes[2].name); + + Assert.Equal(2, newNode.nodes[2].values.Count); + Assert.Equal(2, newNode.nodes[2].nodes.Count); + + Assert.Equal("mm", newNode.nodes[2].values[0].name); + Assert.Equal("12", newNode.nodes[2].values[0].value); + + Assert.Equal("oo", newNode.nodes[2].values[1].name); + Assert.Equal("14", newNode.nodes[2].values[1].value); + + Assert.Equal("INNER_INNER_NODE_21", newNode.nodes[2].nodes[0].name); + + Assert.Equal("pp", newNode.nodes[2].nodes[0].values[0].name); + Assert.Equal("15", newNode.nodes[2].nodes[0].values[0].value); + + Assert.Equal("INNER_INNER_NODE_22", newNode.nodes[2].nodes[1].name); + + Assert.Equal("qq", newNode.nodes[2].nodes[1].values[0].name); + Assert.Equal("16", newNode.nodes[2].nodes[1].values[0].value); + + } + + [Fact] + public void TestCheckNeeds__Exception() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]"), file); + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + + Exception e = new Exception(); + progress.When(p => p.NeedsUnsatisfiedRoot(config2)).Throw(e); + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + string expected = @" +Exception while checking needs on root node : +abc/def/SOME_NODE:NEEDS[mod3] + SOME_NODE:NEEDS[mod3] + { + } +".Replace("\r", null).TrimStart(); + + progress.Received().Exception(config2, expected, e); + + Assert.Equal(new[] { config1, config3 }, root.AllConfigs); + } + + [Fact] + public void TestCheckNeeds__ExceptionWhileLoggingException() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]"), file); + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + + Exception e1 = new Exception(); + Exception e2 = new Exception(); + progress.When(p => p.NeedsUnsatisfiedRoot(config2)).Throw(e1); + progress.WhenForAnyArgs(p => p.Exception(null, null, null)).Throw(e2); + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.ReceivedWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + progress.Received().Exception("Exception while attempting to log an exception", e2); + + Assert.Equal(new[] { config1, config3 }, root.AllConfigs); + } + + private UrlDir.UrlConfig CreateConfig(string name) + { + ConfigNode node = new TestConfigNode(name) + { + { "name", "test" }, + { "foo", "bar" }, + new ConfigNode("INNER_NODE"), + }; + + node.id = "who_uses_this"; + + return UrlBuilder.CreateConfig(node, file); + } + + private void AssertUrlCorrect(string expectedNodeName, UrlDir.UrlConfig originalUrl, UrlDir.UrlConfig observedUrl) + { + // Assert.NotSame(originalUrl, observedUrl); + Assert.Equal(expectedNodeName, observedUrl.type); + + ConfigNode originalNode = originalUrl.config; + ConfigNode observedNode = observedUrl.config; + + Assert.Equal(expectedNodeName, observedNode.name); + + if (originalNode.HasValue("name")) Assert.Equal(originalNode.GetValue("name"), observedUrl.name); + + Assert.Same(originalUrl.parent, observedUrl.parent); + + Assert.Equal(originalNode.id, observedNode.id); + Assert.Equal(originalNode.values.Count, observedNode.values.Count); + Assert.Equal(originalNode.nodes.Count, observedNode.nodes.Count); + + for (int i = 0; i < originalNode.values.Count; i++) + { + Assert.Same(originalNode.values[i], observedNode.values[i]); + } + + for (int i = 0; i < originalNode.nodes.Count; i++) + { + Assert.Same(originalNode.nodes[i], observedNode.nodes[i]); + } + } + } +} From 784e7a80059574c888b4626a711359ce5463a5ae Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 28 Sep 2017 00:10:49 -0700 Subject: [PATCH 127/342] Ensure that final string printed to the screen is the actual status --- ModuleManager/MMPatchLoader.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index ac8bd6bb..e2e07d21 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -338,6 +338,8 @@ private IEnumerator ProcessPatch() CreateCache(progress.PatchedNodeCount); } + StatusUpdate(progress); + #endregion Saving Cache SaveModdedTechTree(); From 13850b617ff7cdadd06a6bf943c7be22cf51b21a Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 28 Sep 2017 00:11:21 -0700 Subject: [PATCH 128/342] Fix up mod list logging * Use a string builder * Print assemblies in a nicer format (table) --- ModuleManager/MMPatchLoader.cs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index e2e07d21..744b0218 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -125,6 +125,7 @@ public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) if (!postPatchCallbacks.Contains(callback)) postPatchCallbacks.Add(callback); } + private IEnumerable GenerateModList(IPatchProgress progress) { #region List of mods @@ -140,7 +141,17 @@ private IEnumerable GenerateModList(IPatchProgress progress) List mods = new List(); - string modlist = "compiling list of loaded mods...\nMod DLLs found:\n"; + StringBuilder modListInfo = new StringBuilder(); + + modListInfo.Append("compiling list of loaded mods...\nMod DLLs found:\n"); + + modListInfo.AppendFormat( + " {0,-40}{1,-30}{2,-30}{3}\n\n", + "Name", + "Assembly Version", + "Assembly File Version", + "SHA256" + ); foreach (AssemblyLoader.LoadedAssembly mod in AssemblyLoader.loadedAssemblies) { @@ -152,6 +163,14 @@ private IEnumerable GenerateModList(IPatchProgress progress) AssemblyName assemblyName = mod.assembly.GetName(); + modListInfo.AppendFormat( + " {0,-40}{1,-30}{2,-30}{3}\n", + assemblyName.Name, + assemblyName.Version, + fileVersionInfo.FileVersion, + FileSHA(mod.assembly.Location) + ); + string modInfo = " " + assemblyName.Name + " v" + assemblyName.Version + @@ -163,13 +182,13 @@ private IEnumerable GenerateModList(IPatchProgress progress) ? " / v" + fileVersionInfo.FileVersion : ""); - modlist += String.Format(" {0,-50} SHA256 {1}\n", modInfo, FileSHA(mod.assembly.Location)); + // modlist += String.Format(" {0,-50} SHA256 {1}\n", modInfo, FileSHA(mod.assembly.Location)); if (!mods.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase)) mods.Add(assemblyName.Name); } - modlist += "Non-DLL mods added (:FOR[xxx]):\n"; + modListInfo.Append("Non-DLL mods added (:FOR[xxx]):\n"); foreach (UrlDir.UrlConfig cfgmod in GameDatabase.Instance.root.AllConfigs) { if (CommandParser.Parse(cfgmod.type, out string name) != Command.Insert) @@ -188,7 +207,7 @@ private IEnumerable GenerateModList(IPatchProgress progress) { // found one, now add it to the list. mods.Add(dependency); - modlist += " " + dependency + "\n"; + modListInfo.AppendFormat(" {0}\n", dependency); } } catch (ArgumentOutOfRangeException) @@ -199,7 +218,7 @@ private IEnumerable GenerateModList(IPatchProgress progress) } } } - modlist += "Mods by directory (sub directories of GameData):\n"; + modListInfo.Append("Mods by directory (sub directories of GameData):\n"); string gameData = Path.Combine(Path.GetFullPath(KSPUtil.ApplicationRootPath), "GameData"); foreach (string subdir in Directory.GetDirectories(gameData)) { @@ -208,10 +227,10 @@ private IEnumerable GenerateModList(IPatchProgress progress) if (!mods.Contains(cleanName, StringComparer.OrdinalIgnoreCase)) { mods.Add(cleanName); - modlist += " " + cleanName + "\n"; + modListInfo.AppendFormat(" {0}\n", cleanName); } } - logger.Info(modlist); + logger.Info(modListInfo.ToString()); mods.Sort(); From cd3a4680ae697b04680743db8120434c42c135d0 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 28 Sep 2017 19:27:38 -0700 Subject: [PATCH 129/342] Use Path.Combine It's more concise then concatenating with the separator char --- ModuleManager/MMPatchLoader.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 744b0218..555ec0ab 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -75,14 +75,13 @@ private void Awake() Instance = this; DontDestroyOnLoad(gameObject); - cachePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "GameData" + Path.DirectorySeparatorChar + "ModuleManager.ConfigCache"; - techTreeFile = "GameData" + Path.DirectorySeparatorChar + "ModuleManager.TechTree"; - techTreePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + techTreeFile; - physicsFile = "GameData" + Path.DirectorySeparatorChar + "ModuleManager.Physics"; - physicsPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + physicsFile; - defaultPhysicsPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "Physics.cfg"; - partDatabasePath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "PartDatabase.cfg"; - shaPath = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "GameData" + Path.DirectorySeparatorChar + "ModuleManager.ConfigSHA"; + cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); + techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); + techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); + physicsFile = Path.Combine("GameData", "ModuleManager.Physics"); + physicsPath = Path.Combine(KSPUtil.ApplicationRootPath, physicsFile); + defaultPhysicsPath = Path.Combine(KSPUtil.ApplicationRootPath, "Physics.cfg"); + shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); logger = new ModLogger("ModuleManager", Debug.logger); } From 901b5a5bc2035719e7e3da3fac1a354ee0cdea56 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 29 Sep 2017 00:31:35 -0700 Subject: [PATCH 130/342] Unnecessary now --- ModuleManager/MMPatchLoader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 555ec0ab..457502a3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -812,8 +812,6 @@ public IEnumerator ApplyPatch(string Stage, IEnumerable patche yield return null; activity = "ModuleManager " + Stage; - - UrlDir.UrlConfig[] allConfigs = GameDatabase.Instance.root.AllConfigs.ToArray(); float nextYield = Time.realtimeSinceStartup + yieldInterval; From a5b1547d4b6020ebad74d1e9cf2668fc9514e58e Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 30 Sep 2017 00:46:11 -0700 Subject: [PATCH 131/342] Improve assembly list * Get rid of unused code * Include KSPAssembly version --- ModuleManager/MMPatchLoader.cs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 457502a3..1a91fa69 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -144,14 +144,19 @@ private IEnumerable GenerateModList(IPatchProgress progress) modListInfo.Append("compiling list of loaded mods...\nMod DLLs found:\n"); + string format = " {0,-40}{1,-25}{2,-25}{3,-25}{4}\n"; + modListInfo.AppendFormat( - " {0,-40}{1,-30}{2,-30}{3}\n\n", + format, "Name", "Assembly Version", "Assembly File Version", + "KSPAssembly Version", "SHA256" ); + modListInfo.Append('\n'); + foreach (AssemblyLoader.LoadedAssembly mod in AssemblyLoader.loadedAssemblies) { @@ -162,25 +167,21 @@ private IEnumerable GenerateModList(IPatchProgress progress) AssemblyName assemblyName = mod.assembly.GetName(); + string kspAssemblyVersion; + if (mod.versionMajor == 0 && mod.versionMinor == 0) + kspAssemblyVersion = ""; + else + kspAssemblyVersion = mod.versionMajor + "." + mod.versionMinor; + modListInfo.AppendFormat( - " {0,-40}{1,-30}{2,-30}{3}\n", + format, assemblyName.Name, assemblyName.Version, fileVersionInfo.FileVersion, + kspAssemblyVersion, FileSHA(mod.assembly.Location) ); - string modInfo = " " + assemblyName.Name - + " v" + assemblyName.Version - + - (fileVersionInfo.ProductVersion != " " && fileVersionInfo.ProductVersion != assemblyName.Version.ToString() - ? " / v" + fileVersionInfo.ProductVersion - : "") - + - (fileVersionInfo.FileVersion != " " &&fileVersionInfo.FileVersion != assemblyName.Version.ToString() && fileVersionInfo.FileVersion != fileVersionInfo.ProductVersion - ? " / v" + fileVersionInfo.FileVersion - : ""); - // modlist += String.Format(" {0,-50} SHA256 {1}\n", modInfo, FileSHA(mod.assembly.Location)); if (!mods.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase)) From 9cf00111aa40a9ae6a39ff6714613c5d68d200a5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 30 Sep 2017 19:43:18 -0700 Subject: [PATCH 132/342] Accidentally removed --- ModuleManager/MMPatchLoader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 1a91fa69..4062735c 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -81,6 +81,7 @@ private void Awake() physicsFile = Path.Combine("GameData", "ModuleManager.Physics"); physicsPath = Path.Combine(KSPUtil.ApplicationRootPath, physicsFile); defaultPhysicsPath = Path.Combine(KSPUtil.ApplicationRootPath, "Physics.cfg"); + partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); logger = new ModLogger("ModuleManager", Debug.logger); From fde3fbc3782c23cbc0527e4128c49d08eb2cfbba Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 1 Oct 2017 11:47:38 -0700 Subject: [PATCH 133/342] Move tracking number of patches from mod list to sorting patches --- ModuleManager/MMPatchLoader.cs | 1 - ModuleManager/PatchExtractor.cs | 1 + ModuleManagerTests/PatchExtractorTest.cs | 14 ++++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 4062735c..7cf68f58 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -194,7 +194,6 @@ private IEnumerable GenerateModList(IPatchProgress progress) { if (CommandParser.Parse(cfgmod.type, out string name) != Command.Insert) { - progress.PatchAdded(); if (name.Contains(":FOR[")) { name = name.RemoveWS(); diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 2283ba0b..23964b2a 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -185,6 +185,7 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable Date: Sun, 1 Oct 2017 11:50:15 -0700 Subject: [PATCH 134/342] Put progress counts in their own object Allows the same counts to be used with a different logger. Also remove unused setter for NeedsUnsatisfiedRootCount --- ModuleManager/IPatchProgress.cs | 2 +- ModuleManager/PatchProgress.cs | 72 ++++++++++++++++++--------------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/ModuleManager/IPatchProgress.cs b/ModuleManager/IPatchProgress.cs index 4c20be50..09696fec 100644 --- a/ModuleManager/IPatchProgress.cs +++ b/ModuleManager/IPatchProgress.cs @@ -10,7 +10,7 @@ public interface IPatchProgress int ErrorCount { get; } int ExceptionCount { get; } int NeedsUnsatisfiedCount { get; } - int PatchedNodeCount { get; set; } + int PatchedNodeCount { get; } float ProgressFraction { get; } int TotalPatchCount { get; } Dictionary ErrorFiles { get; } diff --git a/ModuleManager/PatchProgress.cs b/ModuleManager/PatchProgress.cs index 38439c04..31ee2625 100644 --- a/ModuleManager/PatchProgress.cs +++ b/ModuleManager/PatchProgress.cs @@ -8,23 +8,30 @@ namespace ModuleManager { public class PatchProgress : IPatchProgress { - public int TotalPatchCount { get; private set; } = 0; - - public int AppliedPatchCount { get; private set; } = 0; - - public int PatchedNodeCount { get; set; } = 0; - - public int ErrorCount { get; private set; } = 0; - - public int ExceptionCount { get; private set; } = 0; - - public int NeedsUnsatisfiedRootCount { get; private set; } = 0; - - public int NeedsUnsatisfiedCount { get; private set; } = 0; - - public Dictionary ErrorFiles { get; } = new Dictionary(); + private class ProgressTracker + { + public int totalPatchCount = 0; + public int appliedPatchCount = 0; + public int patchedNodeCount = 0; + public int errorCount = 0; + public int exceptionCount = 0; + public int needsUnsatisfiedRootCount = 0; + public int needsUnsatisfiedCount = 0; + + public Dictionary ErrorFiles { get; } = new Dictionary(); + } + + public int TotalPatchCount => progressTracker.totalPatchCount; + public int AppliedPatchCount => progressTracker.appliedPatchCount; + public int PatchedNodeCount => progressTracker.patchedNodeCount; + public int ErrorCount => progressTracker.errorCount; + public int ExceptionCount => progressTracker.exceptionCount; + public int NeedsUnsatisfiedRootCount => progressTracker.needsUnsatisfiedRootCount; + public int NeedsUnsatisfiedCount => progressTracker.needsUnsatisfiedCount; + public Dictionary ErrorFiles => progressTracker.ErrorFiles; private IBasicLogger logger; + private ProgressTracker progressTracker; public float ProgressFraction { @@ -39,86 +46,87 @@ public float ProgressFraction public PatchProgress(IBasicLogger logger) { this.logger = logger; + progressTracker = new ProgressTracker(); } public void PatchAdded() { - TotalPatchCount += 1; + progressTracker.totalPatchCount += 1; } public void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { logger.Info($"Applying update {patch.SafeUrl()} to {original.SafeUrl()}"); - PatchedNodeCount += 1; + progressTracker.patchedNodeCount += 1; } public void ApplyingCopy(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { logger.Info($"Applying copy {patch.SafeUrl()} to {original.SafeUrl()}"); - PatchedNodeCount += 1; + progressTracker.patchedNodeCount += 1; } public void ApplyingDelete(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { logger.Info($"Applying delete {patch.SafeUrl()} to {original.SafeUrl()}"); - PatchedNodeCount += 1; + progressTracker.patchedNodeCount += 1; } public void PatchApplied() { - AppliedPatchCount += 1; + progressTracker.appliedPatchCount += 1; } public void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its NEEDS"); - NeedsUnsatisfiedCount += 1; - NeedsUnsatisfiedRootCount += 1; + progressTracker.needsUnsatisfiedCount += 1; + progressTracker.needsUnsatisfiedRootCount += 1; } public void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, NodeStack path) { logger.Info($"Deleting node in file {url.parent.url} subnode: {path.GetPath()} as it can't satisfy its NEEDS"); - NeedsUnsatisfiedCount += 1; + progressTracker.needsUnsatisfiedCount += 1; } public void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, NodeStack path, string valName) { logger.Info($"Deleting value in file {url.parent.url} subnode: {path.GetPath()} value: {valName} as it can't satisfy its NEEDS"); - NeedsUnsatisfiedCount += 1; + progressTracker.needsUnsatisfiedCount += 1; } public void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its BEFORE"); - NeedsUnsatisfiedCount += 1; - NeedsUnsatisfiedRootCount += 1; + progressTracker.needsUnsatisfiedCount += 1; + progressTracker.needsUnsatisfiedRootCount += 1; } public void NeedsUnsatisfiedFor(UrlDir.UrlConfig url) { logger.Warning($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its FOR (this shouldn't happen)"); - NeedsUnsatisfiedCount += 1; - NeedsUnsatisfiedRootCount += 1; + progressTracker.needsUnsatisfiedCount += 1; + progressTracker.needsUnsatisfiedRootCount += 1; } public void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its AFTER"); - NeedsUnsatisfiedCount += 1; - NeedsUnsatisfiedRootCount += 1; + progressTracker.needsUnsatisfiedCount += 1; + progressTracker.needsUnsatisfiedRootCount += 1; } public void Error(UrlDir.UrlConfig url, string message) { - ErrorCount += 1; + progressTracker.errorCount += 1; logger.Error(message); RecordErrorFile(url); } public void Exception(string message, Exception exception) { - ExceptionCount += 1; + progressTracker.exceptionCount += 1; logger.Exception(message, exception); } From ce43104059244df30ebad90bfd928ab08ec4bd7d Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 1 Oct 2017 12:37:28 -0700 Subject: [PATCH 135/342] Move exception handling out of FIleSHA Callers should be aware of exceptions anyway --- ModuleManager/MMPatchLoader.cs | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 7cf68f58..e2956b54 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -174,13 +174,23 @@ private IEnumerable GenerateModList(IPatchProgress progress) else kspAssemblyVersion = mod.versionMajor + "." + mod.versionMinor; + string fileSha; + try + { + fileSha = FileSHA(mod.assembly.Location); + } + catch(Exception e) + { + progress.Exception("Exception while generating SHA for assembly " + assemblyName.Name, e); + } + modListInfo.AppendFormat( format, assemblyName.Name, assemblyName.Version, fileVersionInfo.FileVersion, kspAssemblyVersion, - FileSHA(mod.assembly.Location) + fileSha ); // modlist += String.Format(" {0,-50} SHA256 {1}\n", modInfo, FileSHA(mod.assembly.Location)); @@ -491,36 +501,26 @@ private void SaveModdedPhysics() configs[0].config.Save(physicsPath); } - private string FileSHA(string filename) + private static string FileSHA(string filename) { - try - { - if (File.Exists(filename)) - { - System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); + if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); - byte[] data = null; - using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) - { - data = sha.ComputeHash(fs); - } + System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); - string hashedValue = string.Empty; + byte[] data = null; + using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) + { + data = sha.ComputeHash(fs); + } - foreach (byte b in data) - { - hashedValue += String.Format("{0,2:x2}", b); - } + string hashedValue = string.Empty; - return hashedValue; - } - } - catch (Exception e) + foreach (byte b in data) { - logger.Exception("Exception hashing file " + filename, e); - return "0"; + hashedValue += String.Format("{0,2:x2}", b); } - return "0"; + + return hashedValue; } private void IsCacheUpToDate() From 2dc9f9121be35ea1c30ada09c0dc0456cc7b0c1a Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 1 Oct 2017 14:14:55 -0700 Subject: [PATCH 136/342] Extract FileSHA Interacts with the file system so difficult to test unfortunately --- ModuleManager/MMPatchLoader.cs | 25 ++----------------------- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/Utils/FileUtils.cs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 ModuleManager/Utils/FileUtils.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index e2956b54..a02bea21 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -13,6 +13,7 @@ using ModuleManager.Logging; using ModuleManager.Extensions; +using ModuleManager.Utils; using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager @@ -177,7 +178,7 @@ private IEnumerable GenerateModList(IPatchProgress progress) string fileSha; try { - fileSha = FileSHA(mod.assembly.Location); + fileSha = FileUtils.FileSHA(mod.assembly.Location); } catch(Exception e) { @@ -501,28 +502,6 @@ private void SaveModdedPhysics() configs[0].config.Save(physicsPath); } - private static string FileSHA(string filename) - { - if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); - - System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); - - byte[] data = null; - using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) - { - data = sha.ComputeHash(fs); - } - - string hashedValue = string.Empty; - - foreach (byte b in data) - { - hashedValue += String.Format("{0,2:x2}", b); - } - - return hashedValue; - } - private void IsCacheUpToDate() { Stopwatch sw = new Stopwatch(); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 6f5cb2cf..ab16e99f 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -55,6 +55,7 @@ + diff --git a/ModuleManager/Utils/FileUtils.cs b/ModuleManager/Utils/FileUtils.cs new file mode 100644 index 00000000..73cd8dba --- /dev/null +++ b/ModuleManager/Utils/FileUtils.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; + +namespace ModuleManager.Utils +{ + public static class FileUtils + { + public static string FileSHA(string filename) + { + if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); + + System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); + + byte[] data = null; + using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) + { + data = sha.ComputeHash(fs); + } + + string hashedValue = string.Empty; + + foreach (byte b in data) + { + hashedValue += String.Format("{0,2:x2}", b); + } + + return hashedValue; + } + } +} From 6f2693924bbd2b85b5b4ea598a8437f0c9f2a49c Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 1 Oct 2017 14:45:29 -0700 Subject: [PATCH 137/342] Fix unassigned variable --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index a02bea21..d6e0f177 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -175,7 +175,7 @@ private IEnumerable GenerateModList(IPatchProgress progress) else kspAssemblyVersion = mod.versionMajor + "." + mod.versionMinor; - string fileSha; + string fileSha = ""; try { fileSha = FileUtils.FileSHA(mod.assembly.Location); From f3352db53fdd4685c73706845d0fea5e435330ff Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 3 Oct 2017 19:19:45 -0700 Subject: [PATCH 138/342] Make this extractable --- ModuleManager/MMPatchLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index d6e0f177..d21cf5a6 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -127,7 +127,7 @@ public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) postPatchCallbacks.Add(callback); } - private IEnumerable GenerateModList(IPatchProgress progress) + private static IEnumerable GenerateModList(IPatchProgress progress, IBasicLogger logger) { #region List of mods @@ -276,7 +276,7 @@ private IEnumerator ProcessPatch() IPatchProgress progress = new PatchProgress(logger); status = "Pre patch init"; logger.Info(status); - IEnumerable mods = GenerateModList(progress); + IEnumerable mods = GenerateModList(progress, logger); yield return null; From e40426a633c90762f8fb034d561aefb388f57634 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 3 Oct 2017 19:31:45 -0700 Subject: [PATCH 139/342] Extract GenerateModList Unfortunately interacts with AssemblyLoader and the file system so not really testable --- ModuleManager/MMPatchLoader.cs | 125 +------------------------- ModuleManager/ModListGenerator.cs | 139 +++++++++++++++++++++++++++++ ModuleManager/ModuleManager.csproj | 1 + 3 files changed, 141 insertions(+), 124 deletions(-) create mode 100644 ModuleManager/ModListGenerator.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index d21cf5a6..ea39eacd 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -127,129 +127,6 @@ public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) postPatchCallbacks.Add(callback); } - private static IEnumerable GenerateModList(IPatchProgress progress, IBasicLogger logger) - { - #region List of mods - - //string envInfo = "ModuleManager env info\n"; - //envInfo += " " + Environment.OSVersion.Platform + " " + ModuleManager.intPtr.ToInt64().ToString("X16") + "\n"; - //envInfo += " " + Convert.ToString(ModuleManager.intPtr.ToInt64(), 2) + " " + Convert.ToString(ModuleManager.intPtr.ToInt64() >> 63, 2) + "\n"; - //string gamePath = Environment.GetCommandLineArgs()[0]; - //envInfo += " Args: " + gamePath.Split(Path.DirectorySeparatorChar).Last() + " " + string.Join(" ", Environment.GetCommandLineArgs().Skip(1).ToArray()) + "\n"; - //envInfo += " Executable SHA256 " + FileSHA(gamePath); - // - //log(envInfo); - - List mods = new List(); - - StringBuilder modListInfo = new StringBuilder(); - - modListInfo.Append("compiling list of loaded mods...\nMod DLLs found:\n"); - - string format = " {0,-40}{1,-25}{2,-25}{3,-25}{4}\n"; - - modListInfo.AppendFormat( - format, - "Name", - "Assembly Version", - "Assembly File Version", - "KSPAssembly Version", - "SHA256" - ); - - modListInfo.Append('\n'); - - foreach (AssemblyLoader.LoadedAssembly mod in AssemblyLoader.loadedAssemblies) - { - - if (string.IsNullOrEmpty(mod.assembly.Location)) //Diazo Edit for xEvilReeperx AssemblyReloader mod - continue; - - FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(mod.assembly.Location); - - AssemblyName assemblyName = mod.assembly.GetName(); - - string kspAssemblyVersion; - if (mod.versionMajor == 0 && mod.versionMinor == 0) - kspAssemblyVersion = ""; - else - kspAssemblyVersion = mod.versionMajor + "." + mod.versionMinor; - - string fileSha = ""; - try - { - fileSha = FileUtils.FileSHA(mod.assembly.Location); - } - catch(Exception e) - { - progress.Exception("Exception while generating SHA for assembly " + assemblyName.Name, e); - } - - modListInfo.AppendFormat( - format, - assemblyName.Name, - assemblyName.Version, - fileVersionInfo.FileVersion, - kspAssemblyVersion, - fileSha - ); - - // modlist += String.Format(" {0,-50} SHA256 {1}\n", modInfo, FileSHA(mod.assembly.Location)); - - if (!mods.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase)) - mods.Add(assemblyName.Name); - } - - modListInfo.Append("Non-DLL mods added (:FOR[xxx]):\n"); - foreach (UrlDir.UrlConfig cfgmod in GameDatabase.Instance.root.AllConfigs) - { - if (CommandParser.Parse(cfgmod.type, out string name) != Command.Insert) - { - if (name.Contains(":FOR[")) - { - name = name.RemoveWS(); - - // check for FOR[] blocks that don't match loaded DLLs and add them to the pass list - try - { - string dependency = name.Substring(name.IndexOf(":FOR[") + 5); - dependency = dependency.Substring(0, dependency.IndexOf(']')); - if (!mods.Contains(dependency, StringComparer.OrdinalIgnoreCase)) - { - // found one, now add it to the list. - mods.Add(dependency); - modListInfo.AppendFormat(" {0}\n", dependency); - } - } - catch (ArgumentOutOfRangeException) - { - progress.Error(cfgmod, "Skipping :FOR init for line " + name + - ". The line most likely contains a space that should be removed"); - } - } - } - } - modListInfo.Append("Mods by directory (sub directories of GameData):\n"); - string gameData = Path.Combine(Path.GetFullPath(KSPUtil.ApplicationRootPath), "GameData"); - foreach (string subdir in Directory.GetDirectories(gameData)) - { - string name = Path.GetFileName(subdir); - string cleanName = name.RemoveWS(); - if (!mods.Contains(cleanName, StringComparer.OrdinalIgnoreCase)) - { - mods.Add(cleanName); - modListInfo.AppendFormat(" {0}\n", cleanName); - } - } - logger.Info(modListInfo.ToString()); - - mods.Sort(); - - #endregion List of mods - - return mods; - } - private IEnumerator ProcessPatch() { status = "Checking Cache"; @@ -276,7 +153,7 @@ private IEnumerator ProcessPatch() IPatchProgress progress = new PatchProgress(logger); status = "Pre patch init"; logger.Info(status); - IEnumerable mods = GenerateModList(progress, logger); + IEnumerable mods = ModListGenerator.GenerateModList(progress, logger); yield return null; diff --git a/ModuleManager/ModListGenerator.cs b/ModuleManager/ModListGenerator.cs new file mode 100644 index 00000000..951fbac2 --- /dev/null +++ b/ModuleManager/ModListGenerator.cs @@ -0,0 +1,139 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Diagnostics; +using System.Reflection; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using ModuleManager.Utils; + +namespace ModuleManager +{ + public static class ModListGenerator + { + public static IEnumerable GenerateModList(IPatchProgress progress, IBasicLogger logger) + { + #region List of mods + + //string envInfo = "ModuleManager env info\n"; + //envInfo += " " + Environment.OSVersion.Platform + " " + ModuleManager.intPtr.ToInt64().ToString("X16") + "\n"; + //envInfo += " " + Convert.ToString(ModuleManager.intPtr.ToInt64(), 2) + " " + Convert.ToString(ModuleManager.intPtr.ToInt64() >> 63, 2) + "\n"; + //string gamePath = Environment.GetCommandLineArgs()[0]; + //envInfo += " Args: " + gamePath.Split(Path.DirectorySeparatorChar).Last() + " " + string.Join(" ", Environment.GetCommandLineArgs().Skip(1).ToArray()) + "\n"; + //envInfo += " Executable SHA256 " + FileSHA(gamePath); + // + //log(envInfo); + + List mods = new List(); + + StringBuilder modListInfo = new StringBuilder(); + + modListInfo.Append("compiling list of loaded mods...\nMod DLLs found:\n"); + + string format = " {0,-40}{1,-25}{2,-25}{3,-25}{4}\n"; + + modListInfo.AppendFormat( + format, + "Name", + "Assembly Version", + "Assembly File Version", + "KSPAssembly Version", + "SHA256" + ); + + modListInfo.Append('\n'); + + foreach (AssemblyLoader.LoadedAssembly mod in AssemblyLoader.loadedAssemblies) + { + + if (string.IsNullOrEmpty(mod.assembly.Location)) //Diazo Edit for xEvilReeperx AssemblyReloader mod + continue; + + FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(mod.assembly.Location); + + AssemblyName assemblyName = mod.assembly.GetName(); + + string kspAssemblyVersion; + if (mod.versionMajor == 0 && mod.versionMinor == 0) + kspAssemblyVersion = ""; + else + kspAssemblyVersion = mod.versionMajor + "." + mod.versionMinor; + + string fileSha = ""; + try + { + fileSha = FileUtils.FileSHA(mod.assembly.Location); + } + catch (Exception e) + { + progress.Exception("Exception while generating SHA for assembly " + assemblyName.Name, e); + } + + modListInfo.AppendFormat( + format, + assemblyName.Name, + assemblyName.Version, + fileVersionInfo.FileVersion, + kspAssemblyVersion, + fileSha + ); + + // modlist += String.Format(" {0,-50} SHA256 {1}\n", modInfo, FileSHA(mod.assembly.Location)); + + if (!mods.Contains(assemblyName.Name, StringComparer.OrdinalIgnoreCase)) + mods.Add(assemblyName.Name); + } + + modListInfo.Append("Non-DLL mods added (:FOR[xxx]):\n"); + foreach (UrlDir.UrlConfig cfgmod in GameDatabase.Instance.root.AllConfigs) + { + if (CommandParser.Parse(cfgmod.type, out string name) != Command.Insert) + { + if (name.Contains(":FOR[")) + { + name = name.RemoveWS(); + + // check for FOR[] blocks that don't match loaded DLLs and add them to the pass list + try + { + string dependency = name.Substring(name.IndexOf(":FOR[") + 5); + dependency = dependency.Substring(0, dependency.IndexOf(']')); + if (!mods.Contains(dependency, StringComparer.OrdinalIgnoreCase)) + { + // found one, now add it to the list. + mods.Add(dependency); + modListInfo.AppendFormat(" {0}\n", dependency); + } + } + catch (ArgumentOutOfRangeException) + { + progress.Error(cfgmod, "Skipping :FOR init for line " + name + + ". The line most likely contains a space that should be removed"); + } + } + } + } + modListInfo.Append("Mods by directory (sub directories of GameData):\n"); + string gameData = Path.Combine(Path.GetFullPath(KSPUtil.ApplicationRootPath), "GameData"); + foreach (string subdir in Directory.GetDirectories(gameData)) + { + string name = Path.GetFileName(subdir); + string cleanName = name.RemoveWS(); + if (!mods.Contains(cleanName, StringComparer.OrdinalIgnoreCase)) + { + mods.Add(cleanName); + modListInfo.AppendFormat(" {0}\n", cleanName); + } + } + logger.Info(modListInfo.ToString()); + + mods.Sort(); + + #endregion List of mods + + return mods; + } + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index ab16e99f..1ea505c5 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -48,6 +48,7 @@ + From 7a2e18da8eb06d4215e4d034984e98ca0e770af8 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 19 Sep 2017 23:25:58 -0700 Subject: [PATCH 140/342] Add MessageQueue --- ModuleManager/Collections/MessageQueue.cs | 95 +++++++++++++++++++ ModuleManager/ModuleManager.csproj | 1 + .../Collections/MessageQueueTest.cs | 53 +++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 4 files changed, 150 insertions(+) create mode 100644 ModuleManager/Collections/MessageQueue.cs create mode 100644 ModuleManagerTests/Collections/MessageQueueTest.cs diff --git a/ModuleManager/Collections/MessageQueue.cs b/ModuleManager/Collections/MessageQueue.cs new file mode 100644 index 00000000..9f1bc864 --- /dev/null +++ b/ModuleManager/Collections/MessageQueue.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace ModuleManager.Collections +{ + public interface IMessageQueue + { + void Add(T value); + } + + public class MessageQueue : IMessageQueue, IEnumerable + { + public class Enumerator : IEnumerator + { + private readonly MessageQueue queue; + private Node current; + + public Enumerator(MessageQueue queue) + { + this.queue = queue; + } + + public T Current => current.value; + object IEnumerator.Current => Current; + + public void Dispose() { } + + public bool MoveNext() + { + if (current == null) + current = queue.head; + else + current = current.next; + + return current != null; + } + + public void Reset() + { + current = null; + } + } + + private class Node + { + public Node next; + public readonly T value; + + public Node(T value) + { + this.value = value; + } + } + + private readonly object lockObject = new object(); + private Node head; + private Node tail; + + public void Add(T value) + { + Node node = new Node(value); + lock (lockObject) + { + if (head == null) + { + head = node; + tail = node; + } + else + { + tail.next = node; + tail = node; + } + } + } + + public MessageQueue TakeAll() + { + MessageQueue queue = new MessageQueue(); + lock(lockObject) + { + queue.head = head; + queue.tail = tail; + head = null; + tail = null; + } + return queue; + } + + public Enumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 1ea505c5..9d2e7128 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -46,6 +46,7 @@ + diff --git a/ModuleManagerTests/Collections/MessageQueueTest.cs b/ModuleManagerTests/Collections/MessageQueueTest.cs new file mode 100644 index 00000000..9ee116ff --- /dev/null +++ b/ModuleManagerTests/Collections/MessageQueueTest.cs @@ -0,0 +1,53 @@ +using System; +using Xunit; +using ModuleManager.Collections; + +namespace ModuleManagerTests.Collections +{ + public class MessageQueueTest + { + private class TestClass { } + + private MessageQueue queue = new MessageQueue(); + + [Fact] + public void Test__Empty() + { + Assert.Empty(queue); + } + + [Fact] + public void TestAdd() + { + TestClass o1 = new TestClass(); + TestClass o2 = new TestClass(); + TestClass o3 = new TestClass(); + + queue.Add(o1); + queue.Add(o2); + queue.Add(o3); + + Assert.Equal(new[] { o1, o2, o3 }, queue); + } + + [Fact] + public void TestTakeAll() + { + TestClass o1 = new TestClass(); + TestClass o2 = new TestClass(); + TestClass o3 = new TestClass(); + TestClass o4 = new TestClass(); + + queue.Add(o1); + queue.Add(o2); + queue.Add(o3); + + MessageQueue queue2 = queue.TakeAll(); + + queue.Add(o4); + + Assert.Equal(new[] { o4 }, queue); + Assert.Equal(new[] { o1, o2, o3 }, queue2); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 95a8566e..6c7f4b7c 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -52,6 +52,7 @@ + From 7d0c5878014b0e000808f8cb7b04d5665e280aa9 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 19 Sep 2017 23:26:45 -0700 Subject: [PATCH 141/342] Add QueueLogger and supporting classes Allows logging to a queue --- ModuleManager/Logging/ExceptionMessage.cs | 21 +++++++ ModuleManager/Logging/ILogMessage.cs | 9 +++ ModuleManager/Logging/NormalMessage.cs | 22 +++++++ ModuleManager/Logging/QueueLogger.cs | 22 +++++++ ModuleManager/ModuleManager.csproj | 4 ++ .../Logging/ExceptionMessageTest.cs | 22 +++++++ .../Logging/NormalMessageTest.cs | 37 ++++++++++++ ModuleManagerTests/Logging/QueueLoggerTest.cs | 58 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 3 + 9 files changed, 198 insertions(+) create mode 100644 ModuleManager/Logging/ExceptionMessage.cs create mode 100644 ModuleManager/Logging/ILogMessage.cs create mode 100644 ModuleManager/Logging/NormalMessage.cs create mode 100644 ModuleManager/Logging/QueueLogger.cs create mode 100644 ModuleManagerTests/Logging/ExceptionMessageTest.cs create mode 100644 ModuleManagerTests/Logging/NormalMessageTest.cs create mode 100644 ModuleManagerTests/Logging/QueueLoggerTest.cs diff --git a/ModuleManager/Logging/ExceptionMessage.cs b/ModuleManager/Logging/ExceptionMessage.cs new file mode 100644 index 00000000..fceb166d --- /dev/null +++ b/ModuleManager/Logging/ExceptionMessage.cs @@ -0,0 +1,21 @@ +using System; + +namespace ModuleManager.Logging +{ + public class ExceptionMessage : ILogMessage + { + public readonly string message; + public readonly Exception exception; + + public ExceptionMessage(string message, Exception exception) + { + this.message = message; + this.exception = exception; + } + + public void LogTo(IBasicLogger logger) + { + logger.Exception(message, exception); + } + } +} diff --git a/ModuleManager/Logging/ILogMessage.cs b/ModuleManager/Logging/ILogMessage.cs new file mode 100644 index 00000000..3193f973 --- /dev/null +++ b/ModuleManager/Logging/ILogMessage.cs @@ -0,0 +1,9 @@ +using System; + +namespace ModuleManager.Logging +{ + public interface ILogMessage + { + void LogTo(IBasicLogger logger); + } +} diff --git a/ModuleManager/Logging/NormalMessage.cs b/ModuleManager/Logging/NormalMessage.cs new file mode 100644 index 00000000..549f9aa8 --- /dev/null +++ b/ModuleManager/Logging/NormalMessage.cs @@ -0,0 +1,22 @@ +using System; +using UnityEngine; + +namespace ModuleManager.Logging +{ + public class NormalMessage : ILogMessage + { + public readonly LogType logType; + public readonly string message; + + public NormalMessage(LogType logType, string message) + { + this.logType = logType; + this.message = message; + } + + public void LogTo(IBasicLogger logger) + { + logger.Log(logType, message); + } + } +} diff --git a/ModuleManager/Logging/QueueLogger.cs b/ModuleManager/Logging/QueueLogger.cs new file mode 100644 index 00000000..9f3d595a --- /dev/null +++ b/ModuleManager/Logging/QueueLogger.cs @@ -0,0 +1,22 @@ +using System; +using UnityEngine; +using ModuleManager.Collections; + +namespace ModuleManager.Logging +{ + public class QueueLogger : IBasicLogger + { + private readonly IMessageQueue queue; + + public QueueLogger(IMessageQueue queue) + { + this.queue = queue; + } + + public void Log(LogType logType, string message) => queue.Add(new NormalMessage(logType, message)); + public void Info(string message) => Log(LogType.Log, message); + public void Warning(string message) => Log(LogType.Warning, message); + public void Error(string message) => Log(LogType.Error, message); + public void Exception(string message, Exception exception) => queue.Add(new ExceptionMessage(message, exception)); + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 9d2e7128..cc3149a2 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -44,8 +44,12 @@ + + + + diff --git a/ModuleManagerTests/Logging/ExceptionMessageTest.cs b/ModuleManagerTests/Logging/ExceptionMessageTest.cs new file mode 100644 index 00000000..b072ce89 --- /dev/null +++ b/ModuleManagerTests/Logging/ExceptionMessageTest.cs @@ -0,0 +1,22 @@ +using System; +using Xunit; +using NSubstitute; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class ExceptionMessageTest + { + [Fact] + public void TestLogTo() + { + IBasicLogger logger = Substitute.For(); + + Exception e = new Exception(); + ExceptionMessage message = new ExceptionMessage("An exception was thrown", e); + message.LogTo(logger); + + logger.Received().Exception("An exception was thrown", e); + } + } +} diff --git a/ModuleManagerTests/Logging/NormalMessageTest.cs b/ModuleManagerTests/Logging/NormalMessageTest.cs new file mode 100644 index 00000000..c63dd985 --- /dev/null +++ b/ModuleManagerTests/Logging/NormalMessageTest.cs @@ -0,0 +1,37 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class NormalMessageTest + { + private IBasicLogger logger = Substitute.For(); + + [Fact] + public void TestLogTo__Info() + { + NormalMessage message = new NormalMessage(LogType.Log, "everything is ok"); + message.LogTo(logger); + logger.Received().Log(LogType.Log, "everything is ok"); + } + + [Fact] + public void TestLogTo__Warning() + { + NormalMessage message = new NormalMessage(LogType.Warning, "I'm warning you"); + message.LogTo(logger); + logger.Received().Log(LogType.Warning, "I'm warning you"); + } + + [Fact] + public void TestLogTo__Error() + { + NormalMessage message = new NormalMessage(LogType.Error, "You went too far"); + message.LogTo(logger); + logger.Received().Log(LogType.Error, "You went too far"); + } + } +} diff --git a/ModuleManagerTests/Logging/QueueLoggerTest.cs b/ModuleManagerTests/Logging/QueueLoggerTest.cs new file mode 100644 index 00000000..1074c659 --- /dev/null +++ b/ModuleManagerTests/Logging/QueueLoggerTest.cs @@ -0,0 +1,58 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using ModuleManager.Collections; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class QueueLoggerTest + { + private IMessageQueue queue; + private QueueLogger logger; + + public QueueLoggerTest() + { + queue = Substitute.For>(); + logger = new QueueLogger(queue); + } + + [Fact] + public void TestLog() + { + logger.Log(LogType.Log, "this is a log message"); + queue.Received().Add(Arg.Is(m => m.logType == LogType.Log && m.message == "this is a log message")); + } + + [Fact] + public void TestInfo() + { + logger.Info("useful information"); + queue.Received().Add(Arg.Is(m => m.logType == LogType.Log && m.message == "useful information")); + } + + [Fact] + public void TestWarning() + { + logger.Warning("not to alarm you, but something might be wrong"); + queue.Received().Add(Arg.Is(m => m.logType == LogType.Warning && m.message == "not to alarm you, but something might be wrong")); + } + + [Fact] + public void TestError() + { + logger.Error("you broke everything"); + queue.Received().Add(Arg.Is(m => m.logType == LogType.Error && m.message == "you broke everything")); + } + + + [Fact] + public void TestException() + { + Exception e = new Exception(); + logger.Exception("An exception was thrown", e); + queue.Received().Add(Arg.Is(m => m.message == "An exception was thrown" && m.exception == e)); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 6c7f4b7c..03ec31ff 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -53,6 +53,9 @@ + + + From d1975dbdd1bc2c61488644dd8e53ab92c557befd Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Oct 2017 19:22:35 -0700 Subject: [PATCH 142/342] Don't keep track of non-root needs unsatisfied Isn't used anywhere --- ModuleManager/PatchProgress.cs | 10 +--------- ModuleManagerTests/PatchProgressTest.cs | 20 ++++---------------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/ModuleManager/PatchProgress.cs b/ModuleManager/PatchProgress.cs index 31ee2625..83789632 100644 --- a/ModuleManager/PatchProgress.cs +++ b/ModuleManager/PatchProgress.cs @@ -15,7 +15,6 @@ private class ProgressTracker public int patchedNodeCount = 0; public int errorCount = 0; public int exceptionCount = 0; - public int needsUnsatisfiedRootCount = 0; public int needsUnsatisfiedCount = 0; public Dictionary ErrorFiles { get; } = new Dictionary(); @@ -26,7 +25,6 @@ private class ProgressTracker public int PatchedNodeCount => progressTracker.patchedNodeCount; public int ErrorCount => progressTracker.errorCount; public int ExceptionCount => progressTracker.exceptionCount; - public int NeedsUnsatisfiedRootCount => progressTracker.needsUnsatisfiedRootCount; public int NeedsUnsatisfiedCount => progressTracker.needsUnsatisfiedCount; public Dictionary ErrorFiles => progressTracker.ErrorFiles; @@ -38,7 +36,7 @@ public float ProgressFraction get { if (TotalPatchCount > 0) - return (AppliedPatchCount + NeedsUnsatisfiedRootCount) / (float)TotalPatchCount; + return (AppliedPatchCount + NeedsUnsatisfiedCount) / (float)TotalPatchCount; return 0; } } @@ -81,40 +79,34 @@ public void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its NEEDS"); progressTracker.needsUnsatisfiedCount += 1; - progressTracker.needsUnsatisfiedRootCount += 1; } public void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, NodeStack path) { logger.Info($"Deleting node in file {url.parent.url} subnode: {path.GetPath()} as it can't satisfy its NEEDS"); - progressTracker.needsUnsatisfiedCount += 1; } public void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, NodeStack path, string valName) { logger.Info($"Deleting value in file {url.parent.url} subnode: {path.GetPath()} value: {valName} as it can't satisfy its NEEDS"); - progressTracker.needsUnsatisfiedCount += 1; } public void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its BEFORE"); progressTracker.needsUnsatisfiedCount += 1; - progressTracker.needsUnsatisfiedRootCount += 1; } public void NeedsUnsatisfiedFor(UrlDir.UrlConfig url) { logger.Warning($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its FOR (this shouldn't happen)"); progressTracker.needsUnsatisfiedCount += 1; - progressTracker.needsUnsatisfiedRootCount += 1; } public void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its AFTER"); progressTracker.needsUnsatisfiedCount += 1; - progressTracker.needsUnsatisfiedRootCount += 1; } public void Error(UrlDir.UrlConfig url, string message) diff --git a/ModuleManagerTests/PatchProgressTest.cs b/ModuleManagerTests/PatchProgressTest.cs index c46fad9b..8de98c06 100644 --- a/ModuleManagerTests/PatchProgressTest.cs +++ b/ModuleManagerTests/PatchProgressTest.cs @@ -100,16 +100,13 @@ public void TestNeedsUnsatisfiedRoot() UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); Assert.Equal(0, progress.NeedsUnsatisfiedCount); - Assert.Equal(0, progress.NeedsUnsatisfiedRootCount); progress.NeedsUnsatisfiedRoot(config1); Assert.Equal(1, progress.NeedsUnsatisfiedCount); - Assert.Equal(1, progress.NeedsUnsatisfiedRootCount); logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedRoot(config2); Assert.Equal(2, progress.NeedsUnsatisfiedCount); - Assert.Equal(2, progress.NeedsUnsatisfiedRootCount); logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); } @@ -124,11 +121,11 @@ public void TestNeedsUnsatisfiedNode() Assert.Equal(0, progress.NeedsUnsatisfiedCount); progress.NeedsUnsatisfiedNode(config1, stack1); - Assert.Equal(1, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.NeedsUnsatisfiedCount); logger.Received().Info("Deleting node in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedNode(config2, stack2); - Assert.Equal(2, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.NeedsUnsatisfiedCount); logger.Received().Info("Deleting node in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); } @@ -143,11 +140,11 @@ public void TestNeedsUnsatisfiedValue() Assert.Equal(0, progress.NeedsUnsatisfiedCount); progress.NeedsUnsatisfiedValue(config1, stack1, "some_value"); - Assert.Equal(1, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.NeedsUnsatisfiedCount); logger.Received().Info("Deleting value in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE value: some_value as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedValue(config2, stack2, "some_other_value"); - Assert.Equal(2, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.NeedsUnsatisfiedCount); logger.Received().Info("Deleting value in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE value: some_other_value as it can't satisfy its NEEDS"); } @@ -158,16 +155,13 @@ public void TestNeedsUnsatisfiedBefore() UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); Assert.Equal(0, progress.NeedsUnsatisfiedCount); - Assert.Equal(0, progress.NeedsUnsatisfiedRootCount); progress.NeedsUnsatisfiedBefore(config1); Assert.Equal(1, progress.NeedsUnsatisfiedCount); - Assert.Equal(1, progress.NeedsUnsatisfiedRootCount); logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE"); progress.NeedsUnsatisfiedBefore(config2); Assert.Equal(2, progress.NeedsUnsatisfiedCount); - Assert.Equal(2, progress.NeedsUnsatisfiedRootCount); logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE"); } @@ -178,16 +172,13 @@ public void TestNeedsUnsatisfiedFor() UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); Assert.Equal(0, progress.NeedsUnsatisfiedCount); - Assert.Equal(0, progress.NeedsUnsatisfiedRootCount); progress.NeedsUnsatisfiedFor(config1); Assert.Equal(1, progress.NeedsUnsatisfiedCount); - Assert.Equal(1, progress.NeedsUnsatisfiedRootCount); logger.Received().Warning("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)"); progress.NeedsUnsatisfiedFor(config2); Assert.Equal(2, progress.NeedsUnsatisfiedCount); - Assert.Equal(2, progress.NeedsUnsatisfiedRootCount); logger.Received().Warning("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)"); } @@ -198,16 +189,13 @@ public void TestNeedsUnsatisfiedAfter() UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); Assert.Equal(0, progress.NeedsUnsatisfiedCount); - Assert.Equal(0, progress.NeedsUnsatisfiedRootCount); progress.NeedsUnsatisfiedAfter(config1); Assert.Equal(1, progress.NeedsUnsatisfiedCount); - Assert.Equal(1, progress.NeedsUnsatisfiedRootCount); logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER"); progress.NeedsUnsatisfiedAfter(config2); Assert.Equal(2, progress.NeedsUnsatisfiedCount); - Assert.Equal(2, progress.NeedsUnsatisfiedRootCount); logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); } From 78d46359c18677bf34a7cbe0f1b510e2c2b9dc1e Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Oct 2017 22:26:13 -0700 Subject: [PATCH 143/342] Add FatalErrorHandler Allows us to display a message to the user and quit when an unrecoverable error occurs. Can't really be tested unfortunately. --- ModuleManager/FatalErrorHandler.cs | 38 ++++++++++++++++++++++++++++++ ModuleManager/ModuleManager.csproj | 1 + 2 files changed, 39 insertions(+) create mode 100644 ModuleManager/FatalErrorHandler.cs diff --git a/ModuleManager/FatalErrorHandler.cs b/ModuleManager/FatalErrorHandler.cs new file mode 100644 index 00000000..06bf686e --- /dev/null +++ b/ModuleManager/FatalErrorHandler.cs @@ -0,0 +1,38 @@ +using System; +using UnityEngine; + +namespace ModuleManager +{ + public static class FatalErrorHandler + { + public static void HandleFatalError(string message) + { + try + { + PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + new MultiOptionDialog( + "ModuleManagerFatalError", + $"ModuleManager has encountered a fatal error and KSP needs to close.\n\n{message}\n\nPlease see KSP's log for addtional details", + "ModuleManager - Fatal Error", + HighLogic.UISkin, + new Rect(0.5f, 0.5f, 500f, 60f), + new DialogGUIFlexibleSpace(), + new DialogGUIHorizontalLayout( + new DialogGUIFlexibleSpace(), + new DialogGUIButton("Quit", Application.Quit, 140.0f, 30.0f, true), + new DialogGUIFlexibleSpace() + ) + ), + true, + HighLogic.UISkin); + } + catch(Exception ex) + { + Debug.LogError("Exception while trying to create the fatal exception dialog"); + Debug.LogException(ex); + Application.Quit(); + } + } + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index cc3149a2..de2f3035 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -44,6 +44,7 @@ + From 172e2f3c845c42f1a4b4fb14182314891b00939a Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Oct 2017 22:38:41 -0700 Subject: [PATCH 144/342] Add background task support Allows a background task to be run and monitored, including if it exits due to an exception --- ModuleManager/ModuleManager.csproj | 4 + ModuleManager/Threading/BackgroundTask.cs | 33 +++++ ModuleManager/Threading/ITaskStatus.cs | 12 ++ ModuleManager/Threading/TaskStatus.cs | 55 ++++++++ ModuleManager/Threading/TaskStatusWrapper.cs | 19 +++ ModuleManagerTests/ModuleManagerTests.csproj | 2 + .../Threading/BackgroundTaskTest.cs | 72 +++++++++++ .../Threading/TaskStatusTest.cs | 121 ++++++++++++++++++ 8 files changed, 318 insertions(+) create mode 100644 ModuleManager/Threading/BackgroundTask.cs create mode 100644 ModuleManager/Threading/ITaskStatus.cs create mode 100644 ModuleManager/Threading/TaskStatus.cs create mode 100644 ModuleManager/Threading/TaskStatusWrapper.cs create mode 100644 ModuleManagerTests/Threading/BackgroundTaskTest.cs create mode 100644 ModuleManagerTests/Threading/TaskStatusTest.cs diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index de2f3035..4c2f5b19 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -62,8 +62,12 @@ + + + + diff --git a/ModuleManager/Threading/BackgroundTask.cs b/ModuleManager/Threading/BackgroundTask.cs new file mode 100644 index 00000000..64891c04 --- /dev/null +++ b/ModuleManager/Threading/BackgroundTask.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading; + +namespace ModuleManager.Threading +{ + public static class BackgroundTask + { + public static ITaskStatus Start(Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + + TaskStatus status = new TaskStatus(); + + void RunAction() + { + try + { + action(); + status.Finished(); + } + catch (Exception ex) + { + status.Error(ex); + } + } + + Thread thread = new Thread(RunAction); + thread.Start(); + + return new TaskStatusWrapper(status); + } + } +} diff --git a/ModuleManager/Threading/ITaskStatus.cs b/ModuleManager/Threading/ITaskStatus.cs new file mode 100644 index 00000000..e60c76bd --- /dev/null +++ b/ModuleManager/Threading/ITaskStatus.cs @@ -0,0 +1,12 @@ +using System; + +namespace ModuleManager.Threading +{ + public interface ITaskStatus + { + bool IsRunning { get; } + bool IsFinished { get; } + bool IsExitedWithError { get; } + Exception Exception { get; } + } +} diff --git a/ModuleManager/Threading/TaskStatus.cs b/ModuleManager/Threading/TaskStatus.cs new file mode 100644 index 00000000..92f8c237 --- /dev/null +++ b/ModuleManager/Threading/TaskStatus.cs @@ -0,0 +1,55 @@ +using System; + +namespace ModuleManager.Threading +{ + public class TaskStatus : ITaskStatus + { + private bool isRunning = true; + private Exception exception = null; + private object lockObject = new object(); + + public bool IsRunning => isRunning; + public Exception Exception => exception; + + public bool IsFinished + { + get + { + lock (lockObject) + { + return !isRunning && exception == null; + } + } + } + + public bool IsExitedWithError + { + get + { + lock (lockObject) + { + return !isRunning && exception != null; + } + } + } + + public void Finished() + { + lock (lockObject) + { + if (!isRunning) throw new InvalidOperationException("Task is not running"); + isRunning = false; + } + } + + public void Error(Exception exception) + { + lock(lockObject) + { + if (!isRunning) throw new InvalidOperationException("Task is not running"); + this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); + isRunning = false; + } + } + } +} diff --git a/ModuleManager/Threading/TaskStatusWrapper.cs b/ModuleManager/Threading/TaskStatusWrapper.cs new file mode 100644 index 00000000..eff24f78 --- /dev/null +++ b/ModuleManager/Threading/TaskStatusWrapper.cs @@ -0,0 +1,19 @@ +using System; + +namespace ModuleManager.Threading +{ + public class TaskStatusWrapper : ITaskStatus + { + private ITaskStatus inner; + + public TaskStatusWrapper(ITaskStatus inner) + { + this.inner = inner; + } + + public bool IsRunning => inner.IsRunning; + public bool IsFinished => inner.IsFinished; + public bool IsExitedWithError => inner.IsExitedWithError; + public Exception Exception => inner.Exception; + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 03ec31ff..bd5c52f6 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -67,6 +67,8 @@ + + diff --git a/ModuleManagerTests/Threading/BackgroundTaskTest.cs b/ModuleManagerTests/Threading/BackgroundTaskTest.cs new file mode 100644 index 00000000..c9ebc594 --- /dev/null +++ b/ModuleManagerTests/Threading/BackgroundTaskTest.cs @@ -0,0 +1,72 @@ +using System; +using Xunit; +using ModuleManager.Threading; + +namespace ModuleManagerTests.Threading +{ + public class BackgroundTaskTest + { + [Fact] + public void Test__Start() + { + bool finish = false; + void Run() + { + while (!finish) continue; + } + + ITaskStatus status = BackgroundTask.Start(Run); + + Assert.True(status.IsRunning); + Assert.False(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + + finish = true; + + while (status.IsRunning) continue; + + Assert.False(status.IsRunning); + Assert.True(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void Test__Start__Exception() + { + bool finish = false; + Exception ex = new Exception(); + void Run() + { + while (!finish) continue; + throw ex; + } + + ITaskStatus status = BackgroundTask.Start(Run); + + Assert.True(status.IsRunning); + Assert.False(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + + finish = true; + + while (status.IsRunning) continue; + + Assert.False(status.IsRunning); + Assert.False(status.IsFinished); + Assert.True(status.IsExitedWithError); + Assert.Same(ex, status.Exception); + } + + [Fact] + public void Test__Run__ArgumentNull() + { + Assert.Throws(delegate + { + BackgroundTask.Start(null); + }); + } + } +} diff --git a/ModuleManagerTests/Threading/TaskStatusTest.cs b/ModuleManagerTests/Threading/TaskStatusTest.cs new file mode 100644 index 00000000..a32983ff --- /dev/null +++ b/ModuleManagerTests/Threading/TaskStatusTest.cs @@ -0,0 +1,121 @@ +using System; +using Xunit; +using ModuleManager.Threading; + +namespace ModuleManagerTests.Threading +{ + public class TaskStatusTest + { + [Fact] + public void Test__Cosntructor() + { + TaskStatus status = new TaskStatus(); + + Assert.True(status.IsRunning); + Assert.False(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void TestFinished() + { + TaskStatus status = new TaskStatus(); + + status.Finished(); + + Assert.False(status.IsRunning); + Assert.True(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void TestError() + { + TaskStatus status = new TaskStatus(); + Exception ex = new Exception(); + + status.Error(ex); + + Assert.False(status.IsRunning); + Assert.False(status.IsFinished); + Assert.True(status.IsExitedWithError); + Assert.Same(ex, status.Exception); + } + + [Fact] + public void TestFinished__AlreadyFinished() + { + TaskStatus status = new TaskStatus(); + + status.Finished(); + + Assert.Throws(delegate + { + status.Finished(); + }); + + Assert.False(status.IsRunning); + Assert.True(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void TestFinished__AlreadyErrored() + { + TaskStatus status = new TaskStatus(); + Exception ex = new Exception(); + + status.Error(ex); + + Assert.Throws(delegate + { + status.Finished(); + }); + + Assert.False(status.IsRunning); + Assert.False(status.IsFinished); + Assert.True(status.IsExitedWithError); + Assert.Same(ex, status.Exception); + } + + [Fact] + public void TestError__AlreadyFinished() + { + TaskStatus status = new TaskStatus(); + + status.Finished(); + + Assert.Throws(delegate + { + status.Error(new Exception()); + }); + + Assert.False(status.IsRunning); + Assert.True(status.IsFinished); + Assert.False(status.IsExitedWithError); + Assert.Null(status.Exception); + } + + [Fact] + public void TestError__AlreadyErrored() + { + TaskStatus status = new TaskStatus(); + Exception ex = new Exception(); + + status.Error(ex); + + Assert.Throws(delegate + { + status.Error(new Exception()); + }); + + Assert.False(status.IsRunning); + Assert.False(status.IsFinished); + Assert.True(status.IsExitedWithError); + Assert.Same(ex, status.Exception); + } + } +} From 7f9088719a5d478f5b5119fe7123ad06ba17a340 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Oct 2017 22:48:22 -0700 Subject: [PATCH 145/342] Begin creating Progress namespace --- ModuleManager/ModuleManager.csproj | 4 ++-- ModuleManager/{ => Progress}/IPatchProgress.cs | 0 ModuleManager/{ => Progress}/PatchProgress.cs | 0 ModuleManagerTests/ModuleManagerTests.csproj | 2 +- ModuleManagerTests/{ => Progress}/PatchProgressTest.cs | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename ModuleManager/{ => Progress}/IPatchProgress.cs (100%) rename ModuleManager/{ => Progress}/PatchProgress.cs (100%) rename ModuleManagerTests/{ => Progress}/PatchProgressTest.cs (100%) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 4c2f5b19..ae337980 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -43,7 +43,6 @@ - @@ -59,9 +58,10 @@ - + + diff --git a/ModuleManager/IPatchProgress.cs b/ModuleManager/Progress/IPatchProgress.cs similarity index 100% rename from ModuleManager/IPatchProgress.cs rename to ModuleManager/Progress/IPatchProgress.cs diff --git a/ModuleManager/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs similarity index 100% rename from ModuleManager/PatchProgress.cs rename to ModuleManager/Progress/PatchProgress.cs diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index bd5c52f6..d5ce719d 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -65,8 +65,8 @@ - + diff --git a/ModuleManagerTests/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs similarity index 100% rename from ModuleManagerTests/PatchProgressTest.cs rename to ModuleManagerTests/Progress/PatchProgressTest.cs From b0f72c293f1ae36994c73c628eca346906ba59b5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Oct 2017 22:55:30 -0700 Subject: [PATCH 146/342] Finish creating Progress namespace --- ModuleManager/MMPatchLoader.cs | 1 + ModuleManager/ModListGenerator.cs | 1 + ModuleManager/NeedsChecker.cs | 1 + ModuleManager/PatchContext.cs | 1 + ModuleManager/PatchExtractor.cs | 1 + ModuleManager/Progress/IPatchProgress.cs | 2 +- ModuleManager/Progress/PatchProgress.cs | 2 +- ModuleManagerTests/NeedsCheckerTest.cs | 1 + ModuleManagerTests/PatchExtractorTest.cs | 1 + ModuleManagerTests/Progress/PatchProgressTest.cs | 1 + 10 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index ea39eacd..0251dc62 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -14,6 +14,7 @@ using ModuleManager.Logging; using ModuleManager.Extensions; using ModuleManager.Utils; +using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager diff --git a/ModuleManager/ModListGenerator.cs b/ModuleManager/ModListGenerator.cs index 951fbac2..cca98805 100644 --- a/ModuleManager/ModListGenerator.cs +++ b/ModuleManager/ModListGenerator.cs @@ -8,6 +8,7 @@ using ModuleManager.Extensions; using ModuleManager.Logging; using ModuleManager.Utils; +using ModuleManager.Progress; namespace ModuleManager { diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs index 32d46f3d..151bee5f 100644 --- a/ModuleManager/NeedsChecker.cs +++ b/ModuleManager/NeedsChecker.cs @@ -3,6 +3,7 @@ using System.Linq; using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager diff --git a/ModuleManager/PatchContext.cs b/ModuleManager/PatchContext.cs index 2e1bc3e0..ac3b7200 100644 --- a/ModuleManager/PatchContext.cs +++ b/ModuleManager/PatchContext.cs @@ -1,5 +1,6 @@ using System; using ModuleManager.Logging; +using ModuleManager.Progress; namespace ModuleManager { diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 23964b2a..d3425918 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text.RegularExpressions; using ModuleManager.Extensions; +using ModuleManager.Progress; namespace ModuleManager { diff --git a/ModuleManager/Progress/IPatchProgress.cs b/ModuleManager/Progress/IPatchProgress.cs index 09696fec..ae7f5003 100644 --- a/ModuleManager/Progress/IPatchProgress.cs +++ b/ModuleManager/Progress/IPatchProgress.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using NodeStack = ModuleManager.Collections.ImmutableStack; -namespace ModuleManager +namespace ModuleManager.Progress { public interface IPatchProgress { diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index 83789632..0538b035 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -4,7 +4,7 @@ using ModuleManager.Logging; using NodeStack = ModuleManager.Collections.ImmutableStack; -namespace ModuleManager +namespace ModuleManager.Progress { public class PatchProgress : IPatchProgress { diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index 9a25b0e0..41430b05 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -5,6 +5,7 @@ using TestUtils; using ModuleManager; using ModuleManager.Logging; +using ModuleManager.Progress; namespace ModuleManagerTests { diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index fafc1030..453d56e2 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -4,6 +4,7 @@ using NSubstitute; using TestUtils; using ModuleManager; +using ModuleManager.Progress; namespace ModuleManagerTests { diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index 8de98c06..f7866ae6 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -4,6 +4,7 @@ using TestUtils; using ModuleManager; using ModuleManager.Logging; +using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManagerTests From 3dbfbb1ad344efa8326def114cea916d685aefc0 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Oct 2017 22:55:54 -0700 Subject: [PATCH 147/342] Unnecessary directives --- ModuleManager/MMPatchLoader.cs | 1 - ModuleManagerTests/Progress/PatchProgressTest.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 0251dc62..bd87fe03 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -13,7 +13,6 @@ using ModuleManager.Logging; using ModuleManager.Extensions; -using ModuleManager.Utils; using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index f7866ae6..6101f50c 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -2,7 +2,6 @@ using Xunit; using NSubstitute; using TestUtils; -using ModuleManager; using ModuleManager.Logging; using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; From 92ae91f6a8fef397d17f658e67538bc060a50d23 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Oct 2017 23:13:23 -0700 Subject: [PATCH 148/342] Add needs test for and/or and capitalization --- ModuleManagerTests/NeedsCheckerTest.cs | 93 ++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index 41430b05..de9809e9 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -59,6 +59,99 @@ public void TestCheckNeeds__Root() progress.Received().NeedsUnsatisfiedRoot(config7); } + [Fact] + public void TestCheckNeeds__Root__AndOr() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig noNeedsNode = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + + UrlDir.UrlConfig[] needsSatisfiedConfigs = new[] { + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1&mod2]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,mod2]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod2]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1&mod2|mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,mod2|mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod3&mod1]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod,mod1]"), file), + }; + + UrlDir.UrlConfig[] needsUnsatisfiedConfigs = new[] { + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1&mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1&mod2&mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,mod2,mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3|mod4]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod2&mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod2,mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3&mod1|mod2]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3,mod1|mod2]"), file), + }; + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); + Assert.Equal(needsSatisfiedConfigs.Length + 1, configs.Length); + + Assert.Same(noNeedsNode, configs[0]); + + for (int i = 0; i < needsSatisfiedConfigs.Length; i++) + { + AssertUrlCorrect("SOME_NODE", needsSatisfiedConfigs[i], configs[i + 1]); + } + + foreach (UrlDir.UrlConfig config in needsUnsatisfiedConfigs) + { + progress.Received().NeedsUnsatisfiedRoot(config); + } + } + + [Fact] + public void TestCheckNeeds__Root__CaseInsensitive() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig noNeedsNode = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + + UrlDir.UrlConfig[] needsSatisfiedConfigs = new[] { + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[Mod1]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[MOD1]"), file), + }; + + UrlDir.UrlConfig[] needsUnsatisfiedConfigs = new[] { + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[Mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[MOD3]"), file), + }; + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); + Assert.Equal(needsSatisfiedConfigs.Length + 1, configs.Length); + + Assert.Same(noNeedsNode, configs[0]); + + for (int i = 0; i < needsSatisfiedConfigs.Length; i++) + { + AssertUrlCorrect("SOME_NODE", needsSatisfiedConfigs[i], configs[i + 1]); + } + + foreach (UrlDir.UrlConfig config in needsUnsatisfiedConfigs) + { + progress.Received().NeedsUnsatisfiedRoot(config); + } + } + [Fact] public void TestCheckNeeds__Nested() { From 6596b47e64677ee615718e6194d85e7ee6787659 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Oct 2017 23:50:51 -0700 Subject: [PATCH 149/342] Separate out progress counter Make it so that all the values can be incremented but not otherwise messed with. Allow a new progress tracker to be initialized that shares a counter with another but uses a different logger --- ModuleManager/MMPatchLoader.cs | 18 +-- ModuleManager/ModuleManager.csproj | 2 + ModuleManager/Progress/IPatchProgress.cs | 10 +- ModuleManager/Progress/PatchProgress.cs | 61 ++++------ ModuleManager/Progress/ProgressCounter.cs | 18 +++ ModuleManager/Utils/Counter.cs | 19 +++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + .../Progress/PatchProgressTest.cs | 115 ++++++++++-------- ModuleManagerTests/Utils/CounterTest.cs | 56 +++++++++ 9 files changed, 198 insertions(+), 102 deletions(-) create mode 100644 ModuleManager/Progress/ProgressCounter.cs create mode 100644 ModuleManager/Utils/Counter.cs create mode 100644 ModuleManagerTests/Utils/CounterTest.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index bd87fe03..c01219dd 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -216,11 +216,11 @@ private IEnumerator ProcessPatch() #region Saving Cache - if (progress.ErrorCount > 0 || progress.ExceptionCount > 0) + if (progress.Counter.errors > 0 || progress.Counter.exceptions > 0) { - foreach (string file in progress.ErrorFiles.Keys) + foreach (KeyValuePair item in progress.Counter.errorFiles) { - errors += progress.ErrorFiles[file] + " error" + (progress.ErrorFiles[file] > 1 ? "s" : "") + " related to GameData/" + file + errors += item.Value + " error" + (item.Value > 1 ? "s" : "") + " related to GameData/" + item.Key + "\n"; } @@ -242,7 +242,7 @@ private IEnumerator ProcessPatch() status = "Saving Cache"; logger.Info(status); yield return null; - CreateCache(progress.PatchedNodeCount); + CreateCache(progress.Counter.patchedNodes); } StatusUpdate(progress); @@ -639,13 +639,13 @@ private void StatusUpdate(IPatchProgress progress) { progressFraction = progress.ProgressFraction; - status = "ModuleManager: " + progress.PatchedNodeCount + " patch" + (progress.PatchedNodeCount != 1 ? "es" : "") + " applied"; + status = "ModuleManager: " + progress.Counter.patchedNodes + " patch" + (progress.Counter.patchedNodes != 1 ? "es" : "") + " applied"; - if (progress.ErrorCount > 0) - status += ", found " + progress.ErrorCount + " error" + (progress.ErrorCount != 1 ? "s" : "") + ""; + if (progress.Counter.errors > 0) + status += ", found " + progress.Counter.errors + " error" + (progress.Counter.errors != 1 ? "s" : "") + ""; - if (progress.ExceptionCount > 0) - status += ", encountered " + progress.ExceptionCount + " exception" + (progress.ExceptionCount != 1 ? "s" : "") + ""; + if (progress.Counter.exceptions > 0) + status += ", encountered " + progress.Counter.exceptions + " exception" + (progress.Counter.exceptions != 1 ? "s" : "") + ""; } private static void PurgeUnused() diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index ae337980..e3dbe562 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -58,6 +58,7 @@ + @@ -65,6 +66,7 @@ + diff --git a/ModuleManager/Progress/IPatchProgress.cs b/ModuleManager/Progress/IPatchProgress.cs index ae7f5003..5eabfc3c 100644 --- a/ModuleManager/Progress/IPatchProgress.cs +++ b/ModuleManager/Progress/IPatchProgress.cs @@ -1,19 +1,13 @@ using System; -using System.Collections.Generic; using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager.Progress { public interface IPatchProgress { - int AppliedPatchCount { get; } - int ErrorCount { get; } - int ExceptionCount { get; } - int NeedsUnsatisfiedCount { get; } - int PatchedNodeCount { get; } + ProgressCounter Counter { get; } + float ProgressFraction { get; } - int TotalPatchCount { get; } - Dictionary ErrorFiles { get; } void Error(UrlDir.UrlConfig url, string message); void Exception(string message, Exception exception); diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index 0538b035..b039a014 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -8,35 +8,16 @@ namespace ModuleManager.Progress { public class PatchProgress : IPatchProgress { - private class ProgressTracker - { - public int totalPatchCount = 0; - public int appliedPatchCount = 0; - public int patchedNodeCount = 0; - public int errorCount = 0; - public int exceptionCount = 0; - public int needsUnsatisfiedCount = 0; - - public Dictionary ErrorFiles { get; } = new Dictionary(); - } - - public int TotalPatchCount => progressTracker.totalPatchCount; - public int AppliedPatchCount => progressTracker.appliedPatchCount; - public int PatchedNodeCount => progressTracker.patchedNodeCount; - public int ErrorCount => progressTracker.errorCount; - public int ExceptionCount => progressTracker.exceptionCount; - public int NeedsUnsatisfiedCount => progressTracker.needsUnsatisfiedCount; - public Dictionary ErrorFiles => progressTracker.ErrorFiles; + public ProgressCounter Counter { get; private set; } private IBasicLogger logger; - private ProgressTracker progressTracker; public float ProgressFraction { get { - if (TotalPatchCount > 0) - return (AppliedPatchCount + NeedsUnsatisfiedCount) / (float)TotalPatchCount; + if (Counter.totalPatches > 0) + return (Counter.appliedPatches + Counter.needsUnsatisfied) / (float)Counter.totalPatches; return 0; } } @@ -44,41 +25,47 @@ public float ProgressFraction public PatchProgress(IBasicLogger logger) { this.logger = logger; - progressTracker = new ProgressTracker(); + Counter = new ProgressCounter(); + } + + public PatchProgress(IPatchProgress progress, IBasicLogger logger) + { + this.logger = logger; + Counter = progress.Counter; } public void PatchAdded() { - progressTracker.totalPatchCount += 1; + Counter.totalPatches.Increment(); } public void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { logger.Info($"Applying update {patch.SafeUrl()} to {original.SafeUrl()}"); - progressTracker.patchedNodeCount += 1; + Counter.patchedNodes.Increment(); } public void ApplyingCopy(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { logger.Info($"Applying copy {patch.SafeUrl()} to {original.SafeUrl()}"); - progressTracker.patchedNodeCount += 1; + Counter.patchedNodes.Increment(); } public void ApplyingDelete(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) { logger.Info($"Applying delete {patch.SafeUrl()} to {original.SafeUrl()}"); - progressTracker.patchedNodeCount += 1; + Counter.patchedNodes.Increment(); } public void PatchApplied() { - progressTracker.appliedPatchCount += 1; + Counter.appliedPatches.Increment(); } public void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its NEEDS"); - progressTracker.needsUnsatisfiedCount += 1; + Counter.needsUnsatisfied.Increment(); } public void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, NodeStack path) @@ -94,31 +81,31 @@ public void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, NodeStack path, string v public void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its BEFORE"); - progressTracker.needsUnsatisfiedCount += 1; + Counter.needsUnsatisfied.Increment(); } public void NeedsUnsatisfiedFor(UrlDir.UrlConfig url) { logger.Warning($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its FOR (this shouldn't happen)"); - progressTracker.needsUnsatisfiedCount += 1; + Counter.needsUnsatisfied.Increment(); } public void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url) { logger.Info($"Deleting root node in file {url.parent.url} node: {url.type} as it can't satisfy its AFTER"); - progressTracker.needsUnsatisfiedCount += 1; + Counter.needsUnsatisfied.Increment(); } public void Error(UrlDir.UrlConfig url, string message) { - progressTracker.errorCount += 1; + Counter.errors.Increment(); logger.Error(message); RecordErrorFile(url); } public void Exception(string message, Exception exception) { - progressTracker.exceptionCount += 1; + Counter.exceptions.Increment(); logger.Exception(message, exception); } @@ -134,10 +121,10 @@ private void RecordErrorFile(UrlDir.UrlConfig url) if (key[0] == '/') key = key.Substring(1); - if (ErrorFiles.ContainsKey(key)) - ErrorFiles[key] += 1; + if (Counter.errorFiles.ContainsKey(key)) + Counter.errorFiles[key] += 1; else - ErrorFiles[key] = 1; + Counter.errorFiles[key] = 1; } } } diff --git a/ModuleManager/Progress/ProgressCounter.cs b/ModuleManager/Progress/ProgressCounter.cs new file mode 100644 index 00000000..1400ad4f --- /dev/null +++ b/ModuleManager/Progress/ProgressCounter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using ModuleManager.Utils; + +namespace ModuleManager.Progress +{ + public class ProgressCounter + { + public readonly Counter totalPatches = new Counter(); + public readonly Counter appliedPatches = new Counter(); + public readonly Counter patchedNodes = new Counter(); + public readonly Counter errors = new Counter(); + public readonly Counter exceptions = new Counter(); + public readonly Counter needsUnsatisfied = new Counter(); + + public readonly Dictionary errorFiles = new Dictionary(); + } +} diff --git a/ModuleManager/Utils/Counter.cs b/ModuleManager/Utils/Counter.cs new file mode 100644 index 00000000..99929fa4 --- /dev/null +++ b/ModuleManager/Utils/Counter.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ModuleManager.Utils +{ + public class Counter + { + public int Value { get; private set; } = 0; + + public void Increment() + { + Value++; + } + + public static implicit operator int(Counter counter) => counter.Value; + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index d5ce719d..a11e02c5 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -69,6 +69,7 @@ + diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index 6101f50c..4f7e1ecc 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -19,14 +19,33 @@ public PatchProgressTest() progress = new PatchProgress(logger); } + [Fact] + public void Test__Constructor__Nested() + { + IBasicLogger logger2 = Substitute.For(); + PatchProgress progress2 = new PatchProgress(progress, logger2); + + Assert.Same(progress.Counter, progress2.Counter); + + Assert.Equal(0, progress.Counter.patchedNodes); + + UrlDir.UrlConfig original = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("@SOME_NODE")); + + progress2.ApplyingUpdate(original, patch1); + Assert.Equal(1, progress.Counter.patchedNodes); + logger.DidNotReceiveWithAnyArgs().Info(null); + logger2.Received().Info("Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); + } + [Fact] public void TestPatchAdded() { - Assert.Equal(0, progress.TotalPatchCount); + Assert.Equal(0, progress.Counter.totalPatches); progress.PatchAdded(); - Assert.Equal(1, progress.TotalPatchCount); + Assert.Equal(1, progress.Counter.totalPatches); progress.PatchAdded(); - Assert.Equal(2, progress.TotalPatchCount); + Assert.Equal(2, progress.Counter.totalPatches); } [Fact] @@ -36,14 +55,14 @@ public void TestApplyingUpdate() UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("@SOME_NODE")); UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("@SOME_NODE")); - Assert.Equal(0, progress.PatchedNodeCount); + Assert.Equal(0, progress.Counter.patchedNodes); progress.ApplyingUpdate(original, patch1); - Assert.Equal(1, progress.PatchedNodeCount); + Assert.Equal(1, progress.Counter.patchedNodes); logger.Received().Info("Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); progress.ApplyingUpdate(original, patch2); - Assert.Equal(2, progress.PatchedNodeCount); + Assert.Equal(2, progress.Counter.patchedNodes); logger.Received().Info("Applying update pqr/stu/@SOME_NODE to abc/def/SOME_NODE"); } @@ -54,14 +73,14 @@ public void TesApplyingCopy() UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("+SOME_NODE")); UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("+SOME_NODE")); - Assert.Equal(0, progress.PatchedNodeCount); + Assert.Equal(0, progress.Counter.patchedNodes); progress.ApplyingCopy(original, patch1); - Assert.Equal(1, progress.PatchedNodeCount); + Assert.Equal(1, progress.Counter.patchedNodes); logger.Received().Info("Applying copy ghi/jkl/+SOME_NODE to abc/def/SOME_NODE"); progress.ApplyingCopy(original, patch2); - Assert.Equal(2, progress.PatchedNodeCount); + Assert.Equal(2, progress.Counter.patchedNodes); logger.Received().Info("Applying copy pqr/stu/+SOME_NODE to abc/def/SOME_NODE"); } @@ -72,25 +91,25 @@ public void TesApplyingDelete() UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!SOME_NODE")); UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("!SOME_NODE")); - Assert.Equal(0, progress.PatchedNodeCount); + Assert.Equal(0, progress.Counter.patchedNodes); progress.ApplyingDelete(original, patch1); - Assert.Equal(1, progress.PatchedNodeCount); + Assert.Equal(1, progress.Counter.patchedNodes); logger.Received().Info("Applying delete ghi/jkl/!SOME_NODE to abc/def/SOME_NODE"); progress.ApplyingDelete(original, patch2); - Assert.Equal(2, progress.PatchedNodeCount); + Assert.Equal(2, progress.Counter.patchedNodes); logger.Received().Info("Applying delete pqr/stu/!SOME_NODE to abc/def/SOME_NODE"); } [Fact] public void TestPatchApplied() { - Assert.Equal(0, progress.AppliedPatchCount); + Assert.Equal(0, progress.Counter.appliedPatches); progress.PatchApplied(); - Assert.Equal(1, progress.AppliedPatchCount); + Assert.Equal(1, progress.Counter.appliedPatches); progress.PatchApplied(); - Assert.Equal(2, progress.AppliedPatchCount); + Assert.Equal(2, progress.Counter.appliedPatches); } [Fact] @@ -99,14 +118,14 @@ public void TestNeedsUnsatisfiedRoot() UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); progress.NeedsUnsatisfiedRoot(config1); - Assert.Equal(1, progress.NeedsUnsatisfiedCount); + Assert.Equal(1, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedRoot(config2); - Assert.Equal(2, progress.NeedsUnsatisfiedCount); + Assert.Equal(2, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); } @@ -118,14 +137,14 @@ public void TestNeedsUnsatisfiedNode() UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); NodeStack stack2 = new NodeStack(config2.config).Push(new ConfigNode("SOME_OTHER_CHILD_NODE")); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); progress.NeedsUnsatisfiedNode(config1, stack1); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting node in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedNode(config2, stack2); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting node in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); } @@ -137,14 +156,14 @@ public void TestNeedsUnsatisfiedValue() UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); NodeStack stack2 = new NodeStack(config2.config).Push(new ConfigNode("SOME_OTHER_CHILD_NODE")); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); progress.NeedsUnsatisfiedValue(config1, stack1, "some_value"); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting value in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE value: some_value as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedValue(config2, stack2, "some_other_value"); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting value in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE value: some_other_value as it can't satisfy its NEEDS"); } @@ -154,14 +173,14 @@ public void TestNeedsUnsatisfiedBefore() UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); progress.NeedsUnsatisfiedBefore(config1); - Assert.Equal(1, progress.NeedsUnsatisfiedCount); + Assert.Equal(1, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE"); progress.NeedsUnsatisfiedBefore(config2); - Assert.Equal(2, progress.NeedsUnsatisfiedCount); + Assert.Equal(2, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE"); } @@ -171,14 +190,14 @@ public void TestNeedsUnsatisfiedFor() UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); progress.NeedsUnsatisfiedFor(config1); - Assert.Equal(1, progress.NeedsUnsatisfiedCount); + Assert.Equal(1, progress.Counter.needsUnsatisfied); logger.Received().Warning("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)"); progress.NeedsUnsatisfiedFor(config2); - Assert.Equal(2, progress.NeedsUnsatisfiedCount); + Assert.Equal(2, progress.Counter.needsUnsatisfied); logger.Received().Warning("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)"); } @@ -188,14 +207,14 @@ public void TestNeedsUnsatisfiedAfter() UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); - Assert.Equal(0, progress.NeedsUnsatisfiedCount); + Assert.Equal(0, progress.Counter.needsUnsatisfied); progress.NeedsUnsatisfiedAfter(config1); - Assert.Equal(1, progress.NeedsUnsatisfiedCount); + Assert.Equal(1, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER"); progress.NeedsUnsatisfiedAfter(config2); - Assert.Equal(2, progress.NeedsUnsatisfiedCount); + Assert.Equal(2, progress.Counter.needsUnsatisfied); logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); } @@ -205,17 +224,17 @@ public void TestError() UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_OTHER_NODE")); - Assert.Equal(0, progress.ErrorCount); - Assert.False(progress.ErrorFiles.ContainsKey("abc/def.cfg")); + Assert.Equal(0, progress.Counter.errors); + Assert.False(progress.Counter.errorFiles.ContainsKey("abc/def.cfg")); progress.Error(config1, "An error message no one is going to read"); - Assert.Equal(1, progress.ErrorCount); - Assert.Equal(1, progress.ErrorFiles["abc/def.cfg"]); + Assert.Equal(1, progress.Counter.errors); + Assert.Equal(1, progress.Counter.errorFiles["abc/def.cfg"]); logger.Received().Error("An error message no one is going to read"); progress.Error(config2, "Maybe someone will read this one"); - Assert.Equal(2, progress.ErrorCount); - Assert.Equal(2, progress.ErrorFiles["abc/def.cfg"]); + Assert.Equal(2, progress.Counter.errors); + Assert.Equal(2, progress.Counter.errorFiles["abc/def.cfg"]); logger.Received().Error("Maybe someone will read this one"); } @@ -225,14 +244,14 @@ public void TestException() Exception e1 = new Exception(); Exception e2 = new Exception(); - Assert.Equal(0, progress.ExceptionCount); + Assert.Equal(0, progress.Counter.exceptions); progress.Exception("An exception was thrown", e1); - Assert.Equal(1, progress.ExceptionCount); + Assert.Equal(1, progress.Counter.exceptions); logger.Received().Exception("An exception was thrown", e1); progress.Exception("An exception was tossed", e2); - Assert.Equal(2, progress.ExceptionCount); + Assert.Equal(2, progress.Counter.exceptions); logger.Received().Exception("An exception was tossed", e2); } @@ -244,17 +263,17 @@ public void TestException__Url() UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_OTHER_NODE")); Exception e2 = new Exception(); - Assert.Equal(0, progress.ExceptionCount); - Assert.False(progress.ErrorFiles.ContainsKey("abc/def.cfg")); + Assert.Equal(0, progress.Counter.exceptions); + Assert.False(progress.Counter.errorFiles.ContainsKey("abc/def.cfg")); progress.Exception(config1, "An exception was thrown", e1); - Assert.Equal(1, progress.ExceptionCount); - Assert.Equal(1, progress.ErrorFiles["abc/def.cfg"]); + Assert.Equal(1, progress.Counter.exceptions); + Assert.Equal(1, progress.Counter.errorFiles["abc/def.cfg"]); logger.Received().Exception("An exception was thrown", e1); progress.Exception(config2, "An exception was tossed", e2); - Assert.Equal(2, progress.ExceptionCount); - Assert.Equal(2, progress.ErrorFiles["abc/def.cfg"]); + Assert.Equal(2, progress.Counter.exceptions); + Assert.Equal(2, progress.Counter.errorFiles["abc/def.cfg"]); logger.Received().Exception("An exception was tossed", e2); } } diff --git a/ModuleManagerTests/Utils/CounterTest.cs b/ModuleManagerTests/Utils/CounterTest.cs new file mode 100644 index 00000000..4b9c3e86 --- /dev/null +++ b/ModuleManagerTests/Utils/CounterTest.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; +using ModuleManager.Utils; + +namespace ModuleManagerTests.Utils +{ + public class CounterTest + { + [Fact] + public void Test__Constructor() + { + Counter counter = new Counter(); + + Assert.Equal(0, counter.Value); + } + + [Fact] + public void TestIncrement() + { + Counter counter = new Counter(); + + Assert.Equal(0, counter.Value); + + counter.Increment(); + + Assert.Equal(1, counter.Value); + + counter.Increment(); + + Assert.Equal(2, counter.Value); + } + + [Fact] + public void Test__CastAsInt() + { + Counter counter = new Counter(); + int i; + + i = counter; + Assert.Equal(0, i); + + counter.Increment(); + + i = counter; + Assert.Equal(1, i); + + counter.Increment(); + + i = counter; + Assert.Equal(2, i); + } + } +} From 9a05e1b8902043f474c6d8bec4ed88da10ecf328 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 5 Oct 2017 00:05:46 -0700 Subject: [PATCH 150/342] Ensure Counter behaves like an int --- ModuleManager/Utils/Counter.cs | 5 +++++ ModuleManagerTests/Utils/CounterTest.cs | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/ModuleManager/Utils/Counter.cs b/ModuleManager/Utils/Counter.cs index 99929fa4..f4f2b25e 100644 --- a/ModuleManager/Utils/Counter.cs +++ b/ModuleManager/Utils/Counter.cs @@ -14,6 +14,11 @@ public void Increment() Value++; } + public override string ToString() + { + return Value.ToString(); + } + public static implicit operator int(Counter counter) => counter.Value; } } diff --git a/ModuleManagerTests/Utils/CounterTest.cs b/ModuleManagerTests/Utils/CounterTest.cs index 4b9c3e86..2004e36a 100644 --- a/ModuleManagerTests/Utils/CounterTest.cs +++ b/ModuleManagerTests/Utils/CounterTest.cs @@ -33,6 +33,22 @@ public void TestIncrement() Assert.Equal(2, counter.Value); } + [Fact] + public void TestToString() + { + Counter counter = new Counter(); + + Assert.Equal("0", counter.ToString()); + + counter.Increment(); + + Assert.Equal("1", counter.ToString()); + + counter.Increment(); + + Assert.Equal("2", counter.ToString()); + } + [Fact] public void Test__CastAsInt() { From 97368004915876f14236207ea72b490ed85c86e7 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 5 Oct 2017 00:05:57 -0700 Subject: [PATCH 151/342] More unnecessary using --- ModuleManager/Utils/Counter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/ModuleManager/Utils/Counter.cs b/ModuleManager/Utils/Counter.cs index f4f2b25e..dcafce53 100644 --- a/ModuleManager/Utils/Counter.cs +++ b/ModuleManager/Utils/Counter.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace ModuleManager.Utils { From a1af72527f8c6356509a9e12cfecea7c76a86570 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 6 Oct 2017 18:57:44 -0700 Subject: [PATCH 152/342] Add test for ! (not) in :NEEDS --- ModuleManagerTests/NeedsCheckerTest.cs | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index de9809e9..8110cfc4 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -111,6 +111,48 @@ public void TestCheckNeeds__Root__AndOr() } } + [Fact] + public void TestCheckNeeds__Root__Not() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig noNeedsNode = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + + UrlDir.UrlConfig[] needsSatisfiedConfigs = new[] { + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,!mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod1|!mod3]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|!mod2]"), file), + }; + + UrlDir.UrlConfig[] needsUnsatisfiedConfigs = new[] { + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod1]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod1,mod2]"), file), + UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod1&!mod3]"), file), + }; + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); + Assert.Equal(needsSatisfiedConfigs.Length + 1, configs.Length); + + Assert.Same(noNeedsNode, configs[0]); + + for (int i = 0; i < needsSatisfiedConfigs.Length; i++) + { + AssertUrlCorrect("SOME_NODE", needsSatisfiedConfigs[i], configs[i + 1]); + } + + foreach (UrlDir.UrlConfig config in needsUnsatisfiedConfigs) + { + progress.Received().NeedsUnsatisfiedRoot(config); + } + } + [Fact] public void TestCheckNeeds__Root__CaseInsensitive() { From 3a19ff8e6698a8f15660cc8cb2edacd3cb2fcf70 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 12 Oct 2017 22:54:45 -0700 Subject: [PATCH 153/342] More unnecessary using directives --- ModuleManager/Command.cs | 3 --- ModuleManager/CommandParser.cs | 3 --- ModuleManager/Progress/PatchProgress.cs | 1 - 3 files changed, 7 deletions(-) diff --git a/ModuleManager/Command.cs b/ModuleManager/Command.cs index b4c6ffc8..5e7dde49 100644 --- a/ModuleManager/Command.cs +++ b/ModuleManager/Command.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace ModuleManager { diff --git a/ModuleManager/CommandParser.cs b/ModuleManager/CommandParser.cs index fd587441..89029b77 100644 --- a/ModuleManager/CommandParser.cs +++ b/ModuleManager/CommandParser.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace ModuleManager { diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index b039a014..3ea43085 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using ModuleManager.Extensions; using ModuleManager.Logging; using NodeStack = ModuleManager.Collections.ImmutableStack; From e9c341a3e1fcfa5061a441bc9c1455acd60bf275 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 12 Oct 2017 23:03:59 -0700 Subject: [PATCH 154/342] Extract application of patches to its own thread Allows it to not be bound by logging which can be slow --- ModuleManager/MMPatchLoader.cs | 170 +--- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/PatchApplier.cs | 153 ++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PatchApplierTest.cs | 873 +++++++++++++++++++ 5 files changed, 1067 insertions(+), 131 deletions(-) create mode 100644 ModuleManager/PatchApplier.cs create mode 100644 ModuleManagerTests/PatchApplierTest.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index c01219dd..03d64d2d 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -13,6 +13,8 @@ using ModuleManager.Logging; using ModuleManager.Extensions; +using ModuleManager.Collections; +using ModuleManager.Threading; using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; @@ -193,22 +195,48 @@ private IEnumerator ProcessPatch() yield return null; - // :First node - yield return StartCoroutine(ApplyPatch(":FIRST", patchList.firstPatches, progress)); + MessageQueue logQueue = new MessageQueue(); + IBasicLogger patchLogger = new QueueLogger(logQueue); + IPatchProgress threadPatchProgress = new PatchProgress(progress, patchLogger); + PatchApplier applier = new PatchApplier(patchList, GameDatabase.Instance.root, threadPatchProgress, patchLogger); - // any node without a :pass - yield return StartCoroutine(ApplyPatch(":LEGACY (default)", patchList.legacyPatches, progress)); + logger.Info("Starting patch thread"); - foreach (PatchList.ModPass pass in patchList.modPasses) + ITaskStatus patchThread = BackgroundTask.Start(applier.ApplyPatches); + + float nextYield = Time.realtimeSinceStartup + yieldInterval; + + while (patchThread.IsRunning) { - string upperModName = pass.name.ToUpper(); - yield return StartCoroutine(ApplyPatch(":BEFORE[" + upperModName + "]", pass.beforePatches, progress)); - yield return StartCoroutine(ApplyPatch(":FOR[" + upperModName + "]", pass.forPatches, progress)); - yield return StartCoroutine(ApplyPatch(":AFTER[" + upperModName + "]", pass.afterPatches, progress)); + foreach (ILogMessage message in logQueue.TakeAll()) + { + message.LogTo(logger); + } + + if (nextYield < Time.realtimeSinceStartup) + { + nextYield = Time.realtimeSinceStartup + yieldInterval; + StatusUpdate(progress); + activity = applier.Activity; + yield return null; + } } - // :Final node - yield return StartCoroutine(ApplyPatch(":FINAL", patchList.finalPatches, progress)); + // Clear any log messages that might still be in the queue + foreach (ILogMessage message in logQueue.TakeAll()) + { + message.LogTo(logger); + } + + if (patchThread.IsExitedWithError) + { + progress.Exception("The patch runner threw an exception", patchThread.Exception); + FatalErrorHandler.HandleFatalError("The patch runner threw an exception"); + yield break; + } + + logger.Info("Done patching"); + yield return null; PurgeUnused(); @@ -661,126 +689,6 @@ private static void PurgeUnused() #region Applying Patches - // Apply patch to all relevent nodes - public IEnumerator ApplyPatch(string Stage, IEnumerable patches, IPatchProgress progress) - { - StatusUpdate(progress); - logger.Info(Stage + " pass"); - yield return null; - - activity = "ModuleManager " + Stage; - - float nextYield = Time.realtimeSinceStartup + yieldInterval; - - foreach (UrlDir.UrlConfig mod in patches) - { - try - { - string name = mod.type.RemoveWS(); - Command cmd = CommandParser.Parse(name, out string tmp); - - if (cmd == Command.Insert) - { - logger.Warning("Warning - Encountered insert node that should not exist at this stage: " + mod.SafeUrl()); - continue; - } - - string upperName = name.ToUpper(); - PatchContext context = new PatchContext(mod, GameDatabase.Instance.root, logger, progress); - char[] sep = { '[', ']' }; - string condition = ""; - - if (upperName.Contains(":HAS[")) - { - int start = upperName.IndexOf(":HAS["); - condition = name.Substring(start + 5, name.LastIndexOf(']') - start - 5); - name = name.Substring(0, start); - } - - string[] splits = name.Split(sep, 3); - string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; - string type = splits[0].Substring(1); - - foreach (UrlDir.UrlConfig url in GameDatabase.Instance.root.AllConfigs.ToArray()) - { - foreach (string pattern in patterns) - { - bool loop = false; - do - { - if (url.type == type && WildcardMatch(url.name, pattern) - && CheckConstraints(url.config, condition)) - { - switch (cmd) - { - case Command.Edit: - progress.ApplyingUpdate(url, mod); - url.config = ModifyNode(new NodeStack(url.config), mod.config, context); - break; - - case Command.Copy: - ConfigNode clone = ModifyNode(new NodeStack(url.config), mod.config, context); - if (url.config.name != mod.name) - { - progress.ApplyingCopy(url, mod); - url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); - } - else - { - progress.Error(mod, "Error - Error while processing " + mod.config.name + - " the copy needs to have a different name than the parent (use @name = xxx)"); - } - break; - - case Command.Delete: - progress.ApplyingDelete(url, mod); - url.parent.configs.Remove(url); - break; - - default: - logger.Warning("Invalid command encountered on a root node: " + mod.SafeUrl()); - break; - } - // When this special node is found then try to apply the patch once more on the same NODE - if (mod.config.HasNode("MM_PATCH_LOOP")) - { - logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); - loop = true; - } - } - else - { - loop = false; - } - } while (loop); - - } - } - } - catch (Exception e) - { - progress.Exception(mod, "Exception while processing node : " + mod.SafeUrl(), e); - - try - { - logger.Error("Processed node was\n" + mod.PrettyPrint()); - } - catch (Exception ex2) - { - logger.Exception("Exception while attempting to print a node", ex2); - } - } - if (nextYield < Time.realtimeSinceStartup) - { - nextYield = Time.realtimeSinceStartup + yieldInterval; - StatusUpdate(progress); - yield return null; - } - } - StatusUpdate(progress); - yield return null; - } - // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*]+(?:,[^*\d][\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s([+\-*/^!]))?"); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index e3dbe562..174ded44 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -55,6 +55,7 @@ + diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs new file mode 100644 index 00000000..3abc0392 --- /dev/null +++ b/ModuleManager/PatchApplier.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModuleManager.Logging; +using ModuleManager.Extensions; +using ModuleManager.Progress; +using NodeStack = ModuleManager.Collections.ImmutableStack; + +namespace ModuleManager +{ + public class PatchApplier + { + private readonly IBasicLogger logger; + private readonly IPatchProgress progress; + + private readonly UrlDir databaseRoot; + private readonly PatchList patchList; + + public string Activity { get; private set; } + + public PatchApplier(PatchList patchList, UrlDir databaseRoot, IPatchProgress progress, IBasicLogger logger) + { + this.patchList = patchList; + this.databaseRoot = databaseRoot; + this.progress = progress; + this.logger = logger; + } + + public void ApplyPatches() + { + ApplyPatches(":FIRST", patchList.firstPatches); + + // any node without a :pass + ApplyPatches(":LEGACY (default)", patchList.legacyPatches); + + foreach (PatchList.ModPass pass in patchList.modPasses) + { + string upperModName = pass.name.ToUpper(); + ApplyPatches($":BEFORE[{upperModName}]", pass.beforePatches); + ApplyPatches($":FOR[{upperModName}]", pass.forPatches); + ApplyPatches($":AFTER[{upperModName}]", pass.afterPatches); + } + + // :Final node + ApplyPatches(":FINAL", patchList.finalPatches); + } + + private void ApplyPatches(string stage, IEnumerable patches) + { + logger.Info(stage + " pass"); + Activity = "ModuleManager " + stage; + + foreach (UrlDir.UrlConfig mod in patches) + { + try + { + string name = mod.type.RemoveWS(); + Command cmd = CommandParser.Parse(name, out string tmp); + + if (cmd == Command.Insert) + { + logger.Warning("Warning - Encountered insert node that should not exist at this stage: " + mod.SafeUrl()); + continue; + } + + string upperName = name.ToUpper(); + PatchContext context = new PatchContext(mod, databaseRoot, logger, progress); + char[] sep = { '[', ']' }; + string condition = ""; + + if (upperName.Contains(":HAS[")) + { + int start = upperName.IndexOf(":HAS["); + condition = name.Substring(start + 5, name.LastIndexOf(']') - start - 5); + name = name.Substring(0, start); + } + + string[] splits = name.Split(sep, 3); + string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; + string type = splits[0].Substring(1); + + foreach (UrlDir.UrlConfig url in databaseRoot.AllConfigs.ToArray()) + { + foreach (string pattern in patterns) + { + bool loop = false; + do + { + if (url.type == type && MMPatchLoader.WildcardMatch(url.name, pattern) + && MMPatchLoader.CheckConstraints(url.config, condition)) + { + switch (cmd) + { + case Command.Edit: + progress.ApplyingUpdate(url, mod); + url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + break; + + case Command.Copy: + ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) + { + progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); + } + else + { + progress.ApplyingCopy(url, mod); + url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); + } + break; + + case Command.Delete: + progress.ApplyingDelete(url, mod); + url.parent.configs.Remove(url); + break; + + default: + logger.Warning("Invalid command encountered on a root node: " + mod.SafeUrl()); + break; + } + // When this special node is found then try to apply the patch once more on the same NODE + if (mod.config.HasNode("MM_PATCH_LOOP")) + { + logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); + loop = true; + } + } + else + { + loop = false; + } + } while (loop); + } + } + progress.PatchApplied(); + } + catch (Exception e) + { + progress.Exception(mod, "Exception while processing node : " + mod.SafeUrl(), e); + + try + { + logger.Error("Processed node was\n" + mod.PrettyPrint()); + } + catch (Exception ex2) + { + logger.Exception("Exception while attempting to print a node", ex2); + } + } + } + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index a11e02c5..d3ee3803 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -56,6 +56,7 @@ + diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs new file mode 100644 index 00000000..e2656b53 --- /dev/null +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -0,0 +1,873 @@ +using System; +using System.Linq; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; +using ModuleManager.Progress; + +namespace ModuleManagerTests +{ + public class PatchApplierTest + { + private readonly IBasicLogger logger; + private readonly IPatchProgress progress; + private readonly string[] modList = new[] { "mod1", "mod2" }; + private UrlDir databaseRoot; + private UrlDir.UrlFile file; + private readonly PatchList patchList; + private readonly PatchApplier patchApplier; + + public PatchApplierTest() + { + logger = Substitute.For(); + progress = Substitute.For(); + databaseRoot = UrlBuilder.CreateRoot(); + file = UrlBuilder.CreateFile("abc/def.cfg", databaseRoot); + patchList = new PatchList(modList); + patchApplier = new PatchApplier(patchList, databaseRoot, progress, logger); + } + + [Fact] + public void TestApplyPatches__Edit() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "abc" }, + { "foo", "bar" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "def" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") + { + { "name", "ghi" }, + { "jkl", "mno" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART") + { + { "@foo", "baz" }, + { "pqr", "stw" }, + }); + + patchList.firstPatches.Add(patch1); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(1).PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch1); + progress.Received().ApplyingUpdate(config2, patch1); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(3, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "abc" }, + { "foo", "baz" }, + { "pqr", "stw" }, + }, allConfigs[0].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "def" }, + { "pqr", "stw" }, + }, allConfigs[1].config); + + AssertNodesEqual(new TestConfigNode("PORT") + { + { "name", "ghi" }, + { "jkl", "mno" }, + }, allConfigs[2].config); + } + + [Fact] + public void TestApplyPatches__Copy() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "002" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") + { + { "name", "003" }, + { "bbb", "004" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART") + { + { "@name ^", ":^00:01:" }, + { "@aaa", "011" }, + { "ccc", "005" }, + }); + + patchList.firstPatches.Add(patch1); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(1).PatchApplied(); + progress.Received().ApplyingCopy(config1, patch1); + progress.Received().ApplyingCopy(config2, patch1); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(5, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, allConfigs[0].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "002" }, + }, allConfigs[1].config); + + AssertNodesEqual(new TestConfigNode("PORT") + { + { "name", "003" }, + { "bbb", "004" }, + }, allConfigs[2].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "010" }, + { "aaa", "011" }, + { "ccc", "005" }, + }, allConfigs[3].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "012" }, + { "ccc", "005" }, + }, allConfigs[4].config); + } + + [Fact] + public void TestApplyPatches__Copy__AlternateCommand() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "002" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") + { + { "name", "003" }, + { "bbb", "004" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("$PART") + { + { "@name ^", ":^00:01:" }, + { "@aaa", "011" }, + { "ccc", "005" }, + }); + + patchList.firstPatches.Add(patch1); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(1).PatchApplied(); + progress.Received().ApplyingCopy(config1, patch1); + progress.Received().ApplyingCopy(config2, patch1); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(5, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, allConfigs[0].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "002" }, + }, allConfigs[1].config); + + AssertNodesEqual(new TestConfigNode("PORT") + { + { "name", "003" }, + { "bbb", "004" }, + }, allConfigs[2].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "010" }, + { "aaa", "011" }, + { "ccc", "005" }, + }, allConfigs[3].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "012" }, + { "ccc", "005" }, + }, allConfigs[4].config); + } + + [Fact] + public void TestApplyPatches__Delete() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "abc" }, + { "foo", "bar" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "def" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") + { + { "name", "ghi" }, + { "jkl", "mno" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("-PART")); + + patchList.firstPatches.Add(patch1); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(1).PatchApplied(); + progress.Received().ApplyingDelete(config1, patch1); + progress.Received().ApplyingDelete(config2, patch1); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(1, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PORT") + { + { "name", "ghi" }, + { "jkl", "mno" }, + }, allConfigs[0].config); + } + + [Fact] + public void TestApplyPatches__Delete__AlternateCommand() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "abc" }, + { "foo", "bar" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "def" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") + { + { "name", "ghi" }, + { "jkl", "mno" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("-PART")); + + patchList.firstPatches.Add(patch1); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(1).PatchApplied(); + progress.Received().ApplyingDelete(config1, patch1); + progress.Received().ApplyingDelete(config2, patch1); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(1, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PORT") + { + { "name", "ghi" }, + { "jkl", "mno" }, + }, allConfigs[0].config); + } + + [Fact] + public void TestApplyPatches__Name() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "002" }, + { "bbb", "003" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "004" }, + { "ccc", "005" }, + }, file); + + UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") + { + { "name", "006" }, + { "ddd", "007" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000]") + { + { "@aaa", "011" }, + { "eee", "012" }, + }); + + UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART[002]") + { + { "@name", "022" }, + { "@bbb", "013" }, + { "fff", "014" }, + }); + + UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new TestConfigNode("!PART[004]")); + + patchList.firstPatches.Add(patch1); + patchList.firstPatches.Add(patch2); + patchList.firstPatches.Add(patch3); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(3).PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch1); + progress.Received().ApplyingCopy(config2, patch2); + progress.Received().ApplyingDelete(config3, patch3); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(4, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "011" }, + { "eee", "012" }, + }, allConfigs[0].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "002" }, + { "bbb", "003" }, + }, allConfigs[1].config); + + AssertNodesEqual(new TestConfigNode("PORT") + { + { "name", "006" }, + { "ddd", "007" }, + }, allConfigs[2].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "022" }, + { "bbb", "013" }, + { "fff", "014" }, + }, allConfigs[3].config); + } + + [Fact] + public void TestApplyPatches__Name__Wildcard() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "002" }, + { "bbb", "003" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "004" }, + { "ccc", "005" }, + }, file); + + UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") + { + { "name", "006" }, + { "ddd", "007" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[0*0]") + { + { "@aaa", "011" }, + { "eee", "012" }, + }); + + UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART[0*2]") + { + { "@name", "022" }, + { "@bbb", "013" }, + { "fff", "014" }, + }); + + UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new TestConfigNode("!PART[0*4]")); + + patchList.firstPatches.Add(patch1); + patchList.firstPatches.Add(patch2); + patchList.firstPatches.Add(patch3); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(3).PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch1); + progress.Received().ApplyingCopy(config2, patch2); + progress.Received().ApplyingDelete(config3, patch3); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(4, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "011" }, + { "eee", "012" }, + }, allConfigs[0].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "002" }, + { "bbb", "003" }, + }, allConfigs[1].config); + + AssertNodesEqual(new TestConfigNode("PORT") + { + { "name", "006" }, + { "ddd", "007" }, + }, allConfigs[2].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "022" }, + { "bbb", "013" }, + { "fff", "014" }, + }, allConfigs[3].config); + } + + [Fact] + public void TestApplyPatches__Name__Or() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "002" }, + { "bbb", "003" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "004" }, + { "ccc", "005" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "@aaa", "011" }, + { "ddd", "006" }, + }); + + patchList.firstPatches.Add(patch1); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(1).PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch1); + progress.Received().ApplyingUpdate(config2, patch1); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(3, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "011" }, + { "ddd", "006" }, + }, allConfigs[0].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "002" }, + { "bbb", "003" }, + { "ddd", "006" }, + }, allConfigs[1].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "004" }, + { "ccc", "005" }, + }, allConfigs[2].config); + } + + [Fact] + public void TestApplyPatches__Order() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "bbb", "002" }, + }); + + UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "ccc", "003" }, + }); + + UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "ddd", "004" }, + }); + + UrlDir.UrlConfig patch4 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "eee", "005" }, + }); + + UrlDir.UrlConfig patch5 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "fff", "006" }, + }); + + UrlDir.UrlConfig patch6 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "ggg", "007" }, + }); + + UrlDir.UrlConfig patch7 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "hhh", "008" }, + }); + + UrlDir.UrlConfig patch8 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "iii", "009" }, + }); + + UrlDir.UrlConfig patch9 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + { + { "jjj", "010" }, + }); + + patchList.firstPatches.Add(patch1); + patchList.legacyPatches.Add(patch2); + patchList.modPasses["mod1"].beforePatches.Add(patch3); + patchList.modPasses["mod1"].forPatches.Add(patch4); + patchList.modPasses["mod1"].afterPatches.Add(patch5); + patchList.modPasses["mod2"].beforePatches.Add(patch6); + patchList.modPasses["mod2"].forPatches.Add(patch7); + patchList.modPasses["mod2"].afterPatches.Add(patch8); + patchList.finalPatches.Add(patch9); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(9).PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch1); + progress.Received().ApplyingUpdate(config1, patch2); + progress.Received().ApplyingUpdate(config1, patch3); + progress.Received().ApplyingUpdate(config1, patch4); + progress.Received().ApplyingUpdate(config1, patch5); + progress.Received().ApplyingUpdate(config1, patch6); + progress.Received().ApplyingUpdate(config1, patch7); + progress.Received().ApplyingUpdate(config1, patch8); + progress.Received().ApplyingUpdate(config1, patch9); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(1, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + { "bbb", "002" }, + { "ccc", "003" }, + { "ddd", "004" }, + { "eee", "005" }, + { "fff", "006" }, + { "ggg", "007" }, + { "hhh", "008" }, + { "iii", "009" }, + { "jjj", "010" }, + }, allConfigs[0].config); + } + + [Fact] + public void TestApplyPatches__Constraints() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, file); + + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "002" }, + { "bbb", "003" }, + }, file); + + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "004" }, + { "ccc", "005" }, + }, file); + + UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") + { + { "name", "006" }, + { "ddd", "007" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART:HAS[#aaa[001]]") + { + { "@aaa", "011" }, + { "eee", "012" }, + }); + + UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART:HAS[#bbb[003]]") + { + { "@name", "012" }, + { "@bbb", "013" }, + { "fff", "014" }, + }); + + UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new TestConfigNode("!PART:HAS[#ccc[005]]")); + + patchList.firstPatches.Add(patch1); + patchList.firstPatches.Add(patch2); + patchList.firstPatches.Add(patch3); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(3).PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch1); + progress.Received().ApplyingCopy(config2, patch2); + progress.Received().ApplyingDelete(config3, patch3); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(4, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "011" }, + { "eee", "012" }, + }, allConfigs[0].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "002" }, + { "bbb", "003" }, + }, allConfigs[1].config); + + AssertNodesEqual(new TestConfigNode("PORT") + { + { "name", "006" }, + { "ddd", "007" }, + }, allConfigs[2].config); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "012" }, + { "bbb", "013" }, + { "fff", "014" }, + }, allConfigs[3].config); + } + + [Fact] + public void TestApplyPatches__Loop() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "1" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART:HAS[~aaa[>10]]") + { + { "@aaa *", "2" }, + new ConfigNode("MM_PATCH_LOOP"), + }); + + patchList.firstPatches.Add(patch1); + + patchApplier.ApplyPatches(); + + EnsureNoErrors(); + + progress.Received(1).PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch1); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(1, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "16" }, + new ConfigNode("MM_PATCH_LOOP"), + new ConfigNode("MM_PATCH_LOOP"), + new ConfigNode("MM_PATCH_LOOP"), + new ConfigNode("MM_PATCH_LOOP"), + }, allConfigs[0].config); + } + + [Fact] + public void TestApplyPatches__InvalidOperator() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "1" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new ConfigNode("%PART")); + UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new ConfigNode("|PART")); + UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new ConfigNode("#PART")); + UrlDir.UrlConfig patch4 = new UrlDir.UrlConfig(file, new ConfigNode("*PART")); + UrlDir.UrlConfig patch5 = new UrlDir.UrlConfig(file, new ConfigNode("&PART")); + + patchList.firstPatches.Add(patch1); + patchList.firstPatches.Add(patch2); + patchList.firstPatches.Add(patch3); + patchList.firstPatches.Add(patch4); + patchList.firstPatches.Add(patch5); + + patchApplier.ApplyPatches(); + + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + logger.DidNotReceiveWithAnyArgs().Error(null); + logger.DidNotReceiveWithAnyArgs().Exception(null, null); + + logger.Received().Warning("Invalid command encountered on a root node: abc/def/%PART"); + logger.Received().Warning("Invalid command encountered on a root node: abc/def/|PART"); + logger.Received().Warning("Invalid command encountered on a root node: abc/def/#PART"); + logger.Received().Warning("Invalid command encountered on a root node: abc/def/*PART"); + logger.Received().Warning("Invalid command encountered on a root node: abc/def/&PART"); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(1, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "1" }, + }, allConfigs[0].config); + + } + + [Fact] + public void TestApplyPatches__Copy__NameNotChanged() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, file); + + UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART") + { + { "@aaa", "011" }, + { "bbb", "012" }, + }); + + patchList.firstPatches.Add(patch1); + + patchApplier.ApplyPatches(); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + progress.Received().Error(patch1, "Error - when applying copy abc/def/+PART to abc/def/PART - the copy needs to have a different name than the parent (use @name = xxx)"); + + logger.DidNotReceiveWithAnyArgs().Warning(null); + logger.DidNotReceiveWithAnyArgs().Error(null); + logger.DidNotReceiveWithAnyArgs().Exception(null, null); + + progress.Received(1).PatchApplied(); + progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); + + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + Assert.Equal(1, allConfigs.Length); + + AssertNodesEqual(new TestConfigNode("PART") + { + { "name", "000" }, + { "aaa", "001" }, + }, allConfigs[0].config); + } + + private void EnsureNoErrors() + { + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + logger.DidNotReceiveWithAnyArgs().Warning(null); + logger.DidNotReceiveWithAnyArgs().Error(null); + logger.DidNotReceiveWithAnyArgs().Exception(null, null); + } + + private void AssertNodesEqual(ConfigNode expected, ConfigNode actual) + { + Assert.Equal(expected.ToString(), actual.ToString()); + } + } +} From b0e02e098a73aa19b1ef214f75aaaec2790d33ce Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 12 Oct 2017 23:16:03 -0700 Subject: [PATCH 155/342] Test and fix PatchProgress.ProgressFraction Patches are now only counted after needs are checked, so this shouldn't consider needs unsatisfied nodes --- ModuleManager/Progress/PatchProgress.cs | 2 +- .../Progress/PatchProgressTest.cs | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index 3ea43085..4939ffc6 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -16,7 +16,7 @@ public float ProgressFraction get { if (Counter.totalPatches > 0) - return (Counter.appliedPatches + Counter.needsUnsatisfied) / (float)Counter.totalPatches; + return Counter.appliedPatches / (float)Counter.totalPatches; return 0; } } diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index 4f7e1ecc..8d273602 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -276,5 +276,37 @@ public void TestException__Url() Assert.Equal(2, progress.Counter.errorFiles["abc/def.cfg"]); logger.Received().Exception("An exception was tossed", e2); } + + [Fact] + public void TestProgressFraction() + { + Assert.Equal(0, progress.ProgressFraction); + + progress.Counter.needsUnsatisfied.Increment(); + progress.Counter.needsUnsatisfied.Increment(); + + progress.Counter.totalPatches.Increment(); + progress.Counter.totalPatches.Increment(); + progress.Counter.totalPatches.Increment(); + progress.Counter.totalPatches.Increment(); + + Assert.Equal(0, progress.ProgressFraction); + + progress.Counter.appliedPatches.Increment(); + + Assert.Equal(0.25, progress.ProgressFraction); + + progress.Counter.appliedPatches.Increment(); + + Assert.Equal(0.5, progress.ProgressFraction); + + progress.Counter.appliedPatches.Increment(); + + Assert.Equal(0.75, progress.ProgressFraction); + + progress.Counter.appliedPatches.Increment(); + + Assert.Equal(1, progress.ProgressFraction); + } } } From ba24af38d82bae9f5ed17dbf5115a7bed26441a8 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 13 Oct 2017 18:14:15 -0700 Subject: [PATCH 156/342] Tweak --- ModuleManager/MMPatchLoader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 03d64d2d..94291cf1 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -484,6 +484,8 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) { bool noChange = true; StringBuilder changes = new StringBuilder(); + + changes.Append("Changes :\n"); for (int i = 0; i < files.Length; i++) { @@ -516,7 +518,7 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) noChange = false; } if (!noChange) - logger.Info("Changes :\n" + changes.ToString()); + logger.Info(changes.ToString()); return noChange; } From 5fe79fd6b901eed0768ec3452ab65c97389b2971 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 13 Oct 2017 23:17:48 -0700 Subject: [PATCH 157/342] Only convert to array once per pass This is expensive --- ModuleManager/PatchApplier.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 3abc0392..b49d0920 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -50,6 +50,8 @@ private void ApplyPatches(string stage, IEnumerable patches) logger.Info(stage + " pass"); Activity = "ModuleManager " + stage; + UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); + foreach (UrlDir.UrlConfig mod in patches) { try @@ -79,7 +81,7 @@ private void ApplyPatches(string stage, IEnumerable patches) string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; string type = splits[0].Substring(1); - foreach (UrlDir.UrlConfig url in databaseRoot.AllConfigs.ToArray()) + foreach (UrlDir.UrlConfig url in allConfigs) { foreach (string pattern in patterns) { From 20e619287f1e8637476ada51f3a2470200b9b09b Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 13 Oct 2017 23:20:52 -0700 Subject: [PATCH 158/342] Make node matching its own method Saves a level of indentation --- ModuleManager/PatchApplier.cs | 99 +++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index b49d0920..5965b9d3 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -83,56 +83,52 @@ private void ApplyPatches(string stage, IEnumerable patches) foreach (UrlDir.UrlConfig url in allConfigs) { - foreach (string pattern in patterns) + bool loop = false; + do { - bool loop = false; - do + if (IsMatch(url.config, type, patterns, condition)) { - if (url.type == type && MMPatchLoader.WildcardMatch(url.name, pattern) - && MMPatchLoader.CheckConstraints(url.config, condition)) + switch (cmd) { - switch (cmd) - { - case Command.Edit: - progress.ApplyingUpdate(url, mod); - url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); - break; - - case Command.Copy: - ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); - if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) - { - progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); - } - else - { - progress.ApplyingCopy(url, mod); - url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); - } - break; - - case Command.Delete: - progress.ApplyingDelete(url, mod); - url.parent.configs.Remove(url); - break; - - default: - logger.Warning("Invalid command encountered on a root node: " + mod.SafeUrl()); - break; - } - // When this special node is found then try to apply the patch once more on the same NODE - if (mod.config.HasNode("MM_PATCH_LOOP")) - { - logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); - loop = true; - } + case Command.Edit: + progress.ApplyingUpdate(url, mod); + url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + break; + + case Command.Copy: + ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) + { + progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); + } + else + { + progress.ApplyingCopy(url, mod); + url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); + } + break; + + case Command.Delete: + progress.ApplyingDelete(url, mod); + url.parent.configs.Remove(url); + break; + + default: + logger.Warning("Invalid command encountered on a root node: " + mod.SafeUrl()); + break; } - else + // When this special node is found then try to apply the patch once more on the same NODE + if (mod.config.HasNode("MM_PATCH_LOOP")) { - loop = false; + logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); + loop = true; } - } while (loop); - } + } + else + { + loop = false; + } + } while (loop); } progress.PatchApplied(); } @@ -151,5 +147,20 @@ private void ApplyPatches(string stage, IEnumerable patches) } } } + + private static bool IsMatch(ConfigNode node, string type, string[] namePatterns, string constraints) + { + if (node.name != type) return false; + + if (namePatterns.Length > 0) + { + string name = node.GetValue("name"); + if (name == null) return false; + + if (!namePatterns.Any(pattern => MMPatchLoader.WildcardMatch(name, pattern))) return false; + } + + return MMPatchLoader.CheckConstraints(node, constraints); + } } } From 244af1ac93d9c32a017666ff0e59e88d01c75449 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 13 Oct 2017 23:36:55 -0700 Subject: [PATCH 159/342] Loop only applies to edit patches Saves another indentation level. Also remove MM_PATCH_LOOP {} after done --- ModuleManager/PatchApplier.cs | 81 +++++++++++++------------- ModuleManagerTests/PatchApplierTest.cs | 13 +++-- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 5965b9d3..97baed42 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -83,52 +83,49 @@ private void ApplyPatches(string stage, IEnumerable patches) foreach (UrlDir.UrlConfig url in allConfigs) { - bool loop = false; - do + if (IsMatch(url.config, type, patterns, condition)) { - if (IsMatch(url.config, type, patterns, condition)) + switch (cmd) { - switch (cmd) - { - case Command.Edit: + case Command.Edit: + ConfigNode node = url.config; + bool loop = mod.config.HasNode("MM_PATCH_LOOP"); + if (loop) logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); + + do + { progress.ApplyingUpdate(url, mod); - url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); - break; - - case Command.Copy: - ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); - if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) - { - progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); - } - else - { - progress.ApplyingCopy(url, mod); - url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); - } - break; - - case Command.Delete: - progress.ApplyingDelete(url, mod); - url.parent.configs.Remove(url); - break; - - default: - logger.Warning("Invalid command encountered on a root node: " + mod.SafeUrl()); - break; - } - // When this special node is found then try to apply the patch once more on the same NODE - if (mod.config.HasNode("MM_PATCH_LOOP")) - { - logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); - loop = true; - } - } - else - { - loop = false; + node = MMPatchLoader.ModifyNode(new NodeStack(node), mod.config, context); + } while (loop && IsMatch(node, type, patterns, condition)); + + if (loop) node.RemoveNodes("MM_PATCH_LOOP"); + + url.config = node; + break; + + case Command.Copy: + ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) + { + progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); + } + else + { + progress.ApplyingCopy(url, mod); + url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); + } + break; + + case Command.Delete: + progress.ApplyingDelete(url, mod); + url.parent.configs.Remove(url); + break; + + default: + logger.Warning("Invalid command encountered on a root node: " + mod.SafeUrl()); + break; } - } while (loop); + } } progress.PatchApplied(); } diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index e2656b53..332f3a48 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -740,6 +740,7 @@ public void TestApplyPatches__Loop() UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART:HAS[~aaa[>10]]") { { "@aaa *", "2" }, + { "bbb", "002" }, new ConfigNode("MM_PATCH_LOOP"), }); @@ -750,7 +751,9 @@ public void TestApplyPatches__Loop() EnsureNoErrors(); progress.Received(1).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1); + progress.Received(4).ApplyingUpdate(config1, patch1); + + logger.Received().Info("Looping on abc/def/@PART:HAS[~aaa[>10]] to abc/def/PART"); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); @@ -759,10 +762,10 @@ public void TestApplyPatches__Loop() { { "name", "000" }, { "aaa", "16" }, - new ConfigNode("MM_PATCH_LOOP"), - new ConfigNode("MM_PATCH_LOOP"), - new ConfigNode("MM_PATCH_LOOP"), - new ConfigNode("MM_PATCH_LOOP"), + { "bbb", "002" }, + { "bbb", "002" }, + { "bbb", "002" }, + { "bbb", "002" }, }, allConfigs[0].config); } From cdeb5f76dbf574eb5d310c71ea5cc605f7077508 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 14 Oct 2017 00:24:30 -0700 Subject: [PATCH 160/342] Don't convert to an array at all It's not necessary. Also don't use switch - makes things cleaner. It's only 3 cases anyway --- ModuleManager/PatchApplier.cs | 105 +++++++++++++++---------- ModuleManagerTests/PatchApplierTest.cs | 10 +-- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 97baed42..01c912f4 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -50,8 +50,6 @@ private void ApplyPatches(string stage, IEnumerable patches) logger.Info(stage + " pass"); Activity = "ModuleManager " + stage; - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - foreach (UrlDir.UrlConfig mod in patches) { try @@ -64,6 +62,11 @@ private void ApplyPatches(string stage, IEnumerable patches) logger.Warning("Warning - Encountered insert node that should not exist at this stage: " + mod.SafeUrl()); continue; } + else if (cmd != Command.Edit && cmd != Command.Copy && cmd != Command.Delete) + { + logger.Warning("Invalid command encountered on a patch: " + mod.SafeUrl()); + continue; + } string upperName = name.ToUpper(); PatchContext context = new PatchContext(mod, databaseRoot, logger, progress); @@ -81,51 +84,71 @@ private void ApplyPatches(string stage, IEnumerable patches) string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; string type = splits[0].Substring(1); - foreach (UrlDir.UrlConfig url in allConfigs) + foreach (UrlDir.UrlFile file in databaseRoot.AllConfigFiles) { - if (IsMatch(url.config, type, patterns, condition)) + if (cmd == Command.Edit) { - switch (cmd) + foreach (UrlDir.UrlConfig url in file.configs) { - case Command.Edit: - ConfigNode node = url.config; - bool loop = mod.config.HasNode("MM_PATCH_LOOP"); - if (loop) logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); - - do - { - progress.ApplyingUpdate(url, mod); - node = MMPatchLoader.ModifyNode(new NodeStack(node), mod.config, context); - } while (loop && IsMatch(node, type, patterns, condition)); - - if (loop) node.RemoveNodes("MM_PATCH_LOOP"); - - url.config = node; - break; - - case Command.Copy: - ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); - if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) - { - progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); - } - else - { - progress.ApplyingCopy(url, mod); - url.parent.configs.Add(new UrlDir.UrlConfig(url.parent, clone)); - } - break; - - case Command.Delete: - progress.ApplyingDelete(url, mod); - url.parent.configs.Remove(url); - break; + ConfigNode node = url.config; + if (!IsMatch(node, type, patterns, condition)) continue; + bool loop = mod.config.HasNode("MM_PATCH_LOOP"); + if (loop) logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); - default: - logger.Warning("Invalid command encountered on a root node: " + mod.SafeUrl()); - break; + do + { + progress.ApplyingUpdate(url, mod); + node = MMPatchLoader.ModifyNode(new NodeStack(node), mod.config, context); + } while (loop && IsMatch(node, type, patterns, condition)); + + if (loop) node.RemoveNodes("MM_PATCH_LOOP"); + + url.config = node; } } + else if (cmd == Command.Copy) + { + // Avoid checking the new configs we are creating + int count = file.configs.Count; + for (int i = 0; i < count; i++) + { + UrlDir.UrlConfig url = file.configs[i]; + if (!IsMatch(url.config, type, patterns, condition)) continue; + + ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) + { + progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); + } + else + { + progress.ApplyingCopy(url, mod); + file.AddConfig(clone); + } + } + } + else if (cmd == Command.Delete) + { + int j = 0; + while (j < file.configs.Count) + { + UrlDir.UrlConfig url = file.configs[j]; + + if (IsMatch(url.config, type, patterns, condition)) + { + progress.ApplyingDelete(url, mod); + file.configs.RemoveAt(j); + } + else + { + j++; + } + } + } + else + { + throw new NotImplementedException("This code should not be reachable"); + } } progress.PatchApplied(); } diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index 332f3a48..ae5d05b4 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -799,11 +799,11 @@ public void TestApplyPatches__InvalidOperator() logger.DidNotReceiveWithAnyArgs().Error(null); logger.DidNotReceiveWithAnyArgs().Exception(null, null); - logger.Received().Warning("Invalid command encountered on a root node: abc/def/%PART"); - logger.Received().Warning("Invalid command encountered on a root node: abc/def/|PART"); - logger.Received().Warning("Invalid command encountered on a root node: abc/def/#PART"); - logger.Received().Warning("Invalid command encountered on a root node: abc/def/*PART"); - logger.Received().Warning("Invalid command encountered on a root node: abc/def/&PART"); + logger.Received().Warning("Invalid command encountered on a patch: abc/def/%PART"); + logger.Received().Warning("Invalid command encountered on a patch: abc/def/|PART"); + logger.Received().Warning("Invalid command encountered on a patch: abc/def/#PART"); + logger.Received().Warning("Invalid command encountered on a patch: abc/def/*PART"); + logger.Received().Warning("Invalid command encountered on a patch: abc/def/&PART"); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); From 30eeb6f3c1a6ba1525b762ef6583f362b84e64ab Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 14 Oct 2017 00:29:19 -0700 Subject: [PATCH 161/342] Ensure that user gets updates during long passes The patcher can potentially generate log messages faster than the main thread can log them, causing frames that are noticeably long with no updates. This ensures that yields still happen then. Verified that this does not meaningfully affect performance. Previous tests suggest that the time wasted by waiting until the next frame is relatively small. --- ModuleManager/MMPatchLoader.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 94291cf1..b9cb2f20 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -206,22 +206,31 @@ private IEnumerator ProcessPatch() float nextYield = Time.realtimeSinceStartup + yieldInterval; + bool ShouldYield() + { + if (nextYield >= Time.realtimeSinceStartup) return false; + nextYield = Time.realtimeSinceStartup + yieldInterval; + StatusUpdate(progress); + activity = applier.Activity; + return true; + } + while (patchThread.IsRunning) { foreach (ILogMessage message in logQueue.TakeAll()) { message.LogTo(logger); - } - if (nextYield < Time.realtimeSinceStartup) - { - nextYield = Time.realtimeSinceStartup + yieldInterval; - StatusUpdate(progress); - activity = applier.Activity; - yield return null; + if (ShouldYield()) yield return null; } + + if (ShouldYield()) yield return null; } + StatusUpdate(progress); + activity = "ModuleManager - finishing up"; + yield return null; + // Clear any log messages that might still be in the queue foreach (ILogMessage message in logQueue.TakeAll()) { From 7e42de5a4e85a83ef6711885255a9f1b8b64aa0d Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 17 Oct 2017 22:49:55 -0700 Subject: [PATCH 162/342] Without switch, i is valid here --- ModuleManager/PatchApplier.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 01c912f4..09348d5a 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -129,19 +129,19 @@ private void ApplyPatches(string stage, IEnumerable patches) } else if (cmd == Command.Delete) { - int j = 0; - while (j < file.configs.Count) + int i = 0; + while (i < file.configs.Count) { - UrlDir.UrlConfig url = file.configs[j]; + UrlDir.UrlConfig url = file.configs[i]; if (IsMatch(url.config, type, patterns, condition)) { progress.ApplyingDelete(url, mod); - file.configs.RemoveAt(j); + file.configs.RemoveAt(i); } else { - j++; + i++; } } } From dc1a167b0874120c893962e097388bd8be2a84e3 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 17 Oct 2017 23:00:37 -0700 Subject: [PATCH 163/342] Ensure time between each check of the log queue This prevents the queue from being locked too often, slowing down the patching thread --- ModuleManager/MMPatchLoader.cs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index b9cb2f20..8d55a059 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -206,13 +206,16 @@ private IEnumerator ProcessPatch() float nextYield = Time.realtimeSinceStartup + yieldInterval; - bool ShouldYield() + float updateTimeRemaining() { - if (nextYield >= Time.realtimeSinceStartup) return false; - nextYield = Time.realtimeSinceStartup + yieldInterval; - StatusUpdate(progress); - activity = applier.Activity; - return true; + float timeRemaining = nextYield - Time.realtimeSinceStartup; + if (timeRemaining < 0) + { + nextYield = Time.realtimeSinceStartup + yieldInterval; + StatusUpdate(progress); + activity = applier.Activity; + } + return timeRemaining; } while (patchThread.IsRunning) @@ -221,10 +224,12 @@ bool ShouldYield() { message.LogTo(logger); - if (ShouldYield()) yield return null; + if (updateTimeRemaining() < 0) yield return null; } - if (ShouldYield()) yield return null; + float timeRemaining = updateTimeRemaining(); + if (timeRemaining > 0) System.Threading.Thread.Sleep((int)(timeRemaining * 1000)); + yield return null; } StatusUpdate(progress); From ec5310bdfa4ebd10da8de9e675a2150d3beff098 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 17 Oct 2017 23:08:04 -0700 Subject: [PATCH 164/342] Convert to an array initially Apparently it saves a bit of time, and this won't be changed while patches run --- ModuleManager/PatchApplier.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 09348d5a..1cae2925 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -16,6 +16,8 @@ public class PatchApplier private readonly UrlDir databaseRoot; private readonly PatchList patchList; + private readonly UrlDir.UrlFile[] allConfigFiles; + public string Activity { get; private set; } public PatchApplier(PatchList patchList, UrlDir databaseRoot, IPatchProgress progress, IBasicLogger logger) @@ -24,6 +26,8 @@ public PatchApplier(PatchList patchList, UrlDir databaseRoot, IPatchProgress pro this.databaseRoot = databaseRoot; this.progress = progress; this.logger = logger; + + allConfigFiles = databaseRoot.AllConfigFiles.ToArray(); } public void ApplyPatches() @@ -84,7 +88,7 @@ private void ApplyPatches(string stage, IEnumerable patches) string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; string type = splits[0].Substring(1); - foreach (UrlDir.UrlFile file in databaseRoot.AllConfigFiles) + foreach (UrlDir.UrlFile file in allConfigFiles) { if (cmd == Command.Edit) { From 7cc6c5130dc9fe86d6bed7158b1390be88fa5edf Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 17 Oct 2017 23:22:56 -0700 Subject: [PATCH 165/342] Having an actual array here no longer necessary --- ModuleManager/PatchApplier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 1cae2925..376d6319 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -85,7 +85,7 @@ private void ApplyPatches(string stage, IEnumerable patches) } string[] splits = name.Split(sep, 3); - string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : new string[] { null }; + string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : null; string type = splits[0].Substring(1); foreach (UrlDir.UrlFile file in allConfigFiles) @@ -176,7 +176,7 @@ private static bool IsMatch(ConfigNode node, string type, string[] namePatterns, { if (node.name != type) return false; - if (namePatterns.Length > 0) + if (namePatterns != null) { string name = node.GetValue("name"); if (name == null) return false; From bd82d53b561931630a1c5ddda4491e56ab46e727 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 17 Oct 2017 23:23:15 -0700 Subject: [PATCH 166/342] Apparently Linq slows things down I guess it matters at scale --- ModuleManager/PatchApplier.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 376d6319..1dbaa777 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -181,7 +181,17 @@ private static bool IsMatch(ConfigNode node, string type, string[] namePatterns, string name = node.GetValue("name"); if (name == null) return false; - if (!namePatterns.Any(pattern => MMPatchLoader.WildcardMatch(name, pattern))) return false; + bool match = false; + foreach (string pattern in namePatterns) + { + if (MMPatchLoader.WildcardMatch(name, pattern)) + { + match = true; + break; + } + } + + if (!match) return false; } return MMPatchLoader.CheckConstraints(node, constraints); From ce3893f9682c3dae5232491b3ee9f4f97a6e3b2a Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 18 Oct 2017 00:13:16 -0700 Subject: [PATCH 167/342] Improve access of name a bit Looks like GetValue("name") has a bit of overhead, instead we can check if the UrlConfig's type == name --- ModuleManager/PatchApplier.cs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 1dbaa777..4ebbb5fe 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -94,20 +94,17 @@ private void ApplyPatches(string stage, IEnumerable patches) { foreach (UrlDir.UrlConfig url in file.configs) { - ConfigNode node = url.config; - if (!IsMatch(node, type, patterns, condition)) continue; + if (!IsMatch(url, type, patterns, condition)) continue; bool loop = mod.config.HasNode("MM_PATCH_LOOP"); if (loop) logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); do { progress.ApplyingUpdate(url, mod); - node = MMPatchLoader.ModifyNode(new NodeStack(node), mod.config, context); - } while (loop && IsMatch(node, type, patterns, condition)); + url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + } while (loop && IsMatch(url, type, patterns, condition)); - if (loop) node.RemoveNodes("MM_PATCH_LOOP"); - - url.config = node; + if (loop) url.config.RemoveNodes("MM_PATCH_LOOP"); } } else if (cmd == Command.Copy) @@ -117,7 +114,7 @@ private void ApplyPatches(string stage, IEnumerable patches) for (int i = 0; i < count; i++) { UrlDir.UrlConfig url = file.configs[i]; - if (!IsMatch(url.config, type, patterns, condition)) continue; + if (!IsMatch(url, type, patterns, condition)) continue; ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) @@ -138,7 +135,7 @@ private void ApplyPatches(string stage, IEnumerable patches) { UrlDir.UrlConfig url = file.configs[i]; - if (IsMatch(url.config, type, patterns, condition)) + if (IsMatch(url, type, patterns, condition)) { progress.ApplyingDelete(url, mod); file.configs.RemoveAt(i); @@ -172,19 +169,18 @@ private void ApplyPatches(string stage, IEnumerable patches) } } - private static bool IsMatch(ConfigNode node, string type, string[] namePatterns, string constraints) + private static bool IsMatch(UrlDir.UrlConfig url, string type, string[] namePatterns, string constraints) { - if (node.name != type) return false; + if (url.type != type) return false; if (namePatterns != null) { - string name = node.GetValue("name"); - if (name == null) return false; + if (url.name == url.type) return false; bool match = false; foreach (string pattern in namePatterns) { - if (MMPatchLoader.WildcardMatch(name, pattern)) + if (MMPatchLoader.WildcardMatch(url.name, pattern)) { match = true; break; @@ -194,7 +190,7 @@ private static bool IsMatch(ConfigNode node, string type, string[] namePatterns, if (!match) return false; } - return MMPatchLoader.CheckConstraints(node, constraints); + return MMPatchLoader.CheckConstraints(url.config, constraints); } } } From 203ad88debd4cd772d754da569e35d0efd241943 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 18 Oct 2017 00:43:09 -0700 Subject: [PATCH 168/342] Move loop out of loop This is all a bit loopy --- ModuleManager/PatchApplier.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 4ebbb5fe..5e30ad02 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -88,6 +88,8 @@ private void ApplyPatches(string stage, IEnumerable patches) string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : null; string type = splits[0].Substring(1); + bool loop = mod.config.HasNode("MM_PATCH_LOOP"); + foreach (UrlDir.UrlFile file in allConfigFiles) { if (cmd == Command.Edit) @@ -95,7 +97,6 @@ private void ApplyPatches(string stage, IEnumerable patches) foreach (UrlDir.UrlConfig url in file.configs) { if (!IsMatch(url, type, patterns, condition)) continue; - bool loop = mod.config.HasNode("MM_PATCH_LOOP"); if (loop) logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); do From 73f7b397ab6cc0930be850a9f8c33dc86a1b58dc Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 12 Nov 2017 00:41:49 -0800 Subject: [PATCH 169/342] case should match filename matters on some filesystems --- TestUtils/{URLBuilder.cs => UrlBuilder.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename TestUtils/{URLBuilder.cs => UrlBuilder.cs} (100%) diff --git a/TestUtils/URLBuilder.cs b/TestUtils/UrlBuilder.cs similarity index 100% rename from TestUtils/URLBuilder.cs rename to TestUtils/UrlBuilder.cs From 2caefbd81811147d23444796b5caffdb38ce07ba Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 2 Dec 2017 11:38:58 +0100 Subject: [PATCH 170/342] v3.0.0 for KSP 1.3.1 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 4ed5322e..26dbe0ee 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("2.8.1")] +[assembly: AssemblyVersion("3.0.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From bba0efef25a46f00bfd36101197cd7ae25a6f358 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Tue, 5 Dec 2017 21:43:52 +0100 Subject: [PATCH 171/342] Add a -mm-dump cmd line option and redo the export Now uses the same directory sub tree as GameData --- ModuleManager/MMPatchLoader.cs | 7 ++- ModuleManager/ModuleManager.cs | 89 +++++++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 8d55a059..bcaeb987 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -384,6 +384,11 @@ float updateTimeRemaining() yield return null; + if (ModuleManager.dumpPostPatch) + ModuleManager.OutputAllConfigs(); + + yield return null; + ready = true; } @@ -391,7 +396,7 @@ private void LoadPhysicsConfig() { logger.Info("Loading Physics.cfg"); UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); - // need to use a file with a cfg extenssion to get the right fileType or you can't AddConfig on it + // need to use a file with a cfg extension to get the right fileType or you can't AddConfig on it physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); // Since it loaded the default config badly (sub node only) we clear it first physicsUrlFile.configs.Clear(); diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index bd6867ac..a3931c78 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -32,6 +32,7 @@ public class ModuleManager : MonoBehaviour private bool nyan = false; private bool nCats = false; + public static bool dumpPostPatch = false; private PopupDialog menu; @@ -114,6 +115,8 @@ internal void Awake() nCats = catDay || Environment.GetCommandLineArgs().Contains("-ncats"); + dumpPostPatch = Environment.GetCommandLineArgs().Contains("-mm-dump"); + loadedInScene = true; } @@ -329,26 +332,86 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) ScreenMessages.PostScreenMessage("Database reloading finished", 1, ScreenMessageStyle.UPPER_CENTER); } - private static void OutputAllConfigs() + public static void OutputAllConfigs() { - string path = KSPUtil.ApplicationRootPath + Path.DirectorySeparatorChar + "_MMCfgOutput" - + Path.DirectorySeparatorChar; - Directory.CreateDirectory(path); - - foreach (UrlDir.UrlConfig d in GameDatabase.Instance.root.AllConfigs) + string path = KSPUtil.ApplicationRootPath + "/_MMCfgOutput/"; + try { - string file = d.url.Replace('/', '.').Replace(':', '.'); - string filePath = path + file + ".cfg"; - try + Directory.CreateDirectory(path); + foreach (string file in Directory.GetFiles(path)) { - - File.WriteAllText(filePath, d.config.ToString()); + File.Delete(file); + } + foreach (string dir in Directory.GetDirectories(path)) + { + Directory.Delete(dir, true); } - catch (Exception e) + } + catch (IOException ioException) + { + Log("Exception while cleaning the export dir\n" + ioException); + } + catch (UnauthorizedAccessException unauthorizedAccessException) + { + Log("Exception while cleaning the export dir\n" + unauthorizedAccessException); + } + Stack dirs = new Stack(); + dirs.Push(GameDatabase.Instance.root); + Stack paths = new Stack(); + paths.Push(""); + + try + { + while (dirs.Count > 0) { - Log("Exception while trying to write the file " + filePath + "\n" + e); + var currentDir = dirs.Pop(); + string currentPath = paths.Pop(); + + foreach (UrlDir.UrlFile urlFile in currentDir.files) + { + if (urlFile.fileType == UrlDir.FileType.Config) + { + string dirPath = path + currentPath; + if (!Directory.Exists(dirPath)) + { + Directory.CreateDirectory(dirPath); + } + + Log("Exporting " + currentPath + urlFile.name + "." + urlFile.fileExtension); + string filePath = dirPath + urlFile.name + "." + urlFile.fileExtension; + foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) + { + try + { + File.AppendAllText(filePath, urlConfig.config.ToString()); + } + catch (Exception e) + { + Log("Exception while trying to write the file " + filePath + "\n" + e); + } + } + } + } + + foreach (UrlDir urlDir in currentDir.children) + { + dirs.Push(urlDir); + paths.Push(currentPath + urlDir.name + "/"); + } } } + catch (DirectoryNotFoundException directoryNotFoundException) + { + Log("Exception while exporting the cfg\n" + directoryNotFoundException); + } + catch (IOException ioException) + { + Log("Exception while exporting the cfg\n" + ioException); + } + catch (UnauthorizedAccessException unauthorizedAccessException) + { + Log("Exception while exporting the cfg\n" + unauthorizedAccessException); + } } #endregion GUI stuff. From 60171d951e7a5d7a1954119afdc0d9b82df894d2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 5 Dec 2017 22:25:26 -0800 Subject: [PATCH 172/342] Fix NEEDS checking for inner nodes/values Didn't work if you had both top level NEEDS and NEEDS on a subnode/value since it was checking NEEDS on the wrong node in that case --- ModuleManager/NeedsChecker.cs | 6 +-- ModuleManagerTests/NeedsCheckerTest.cs | 52 ++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs index 151bee5f..c0078c87 100644 --- a/ModuleManager/NeedsChecker.cs +++ b/ModuleManager/NeedsChecker.cs @@ -41,14 +41,14 @@ public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, } // Recursively check the contents - PatchContext context = new PatchContext(mod, gameDatabaseRoot, logger, progress); - CheckNeeds(new NodeStack(mod.config), context, mods); + PatchContext context = new PatchContext(currentMod, gameDatabaseRoot, logger, progress); + CheckNeeds(new NodeStack(currentMod.config), context, mods); } catch (Exception ex) { try { - progress.Exception(currentMod, "Exception while checking needs on root node :\n" + currentMod.PrettyPrint(), ex); + progress.Exception(mod, "Exception while checking needs on root node :\n" + mod.PrettyPrint(), ex); } catch (Exception ex2) { diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index 8110cfc4..ee7b9950 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -6,6 +6,8 @@ using ModuleManager; using ModuleManager.Logging; using ModuleManager.Progress; +using ModuleManager.Extensions; +using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManagerTests { @@ -346,6 +348,56 @@ public void TestCheckNeeds__Nested() } + [Fact] + public void TestCheckNeeds__RootAndNested() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("SOME_NODE:NEEDS[mod1]") + { + { "aa:NEEDS[mod2]", "00" }, + { "bb:NEEDS[mod3]", "01" }, + new TestConfigNode("INNER_NODE_1:NEEDS[mod2]") + { + { "cc", "02" }, + }, + new TestConfigNode("INNER_NODE_2:NEEDS[mod3]") + { + { "dd", "03" }, + }, + }, file); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("SOME_OTHER_NODE:NEEDS[mod3]"), file); + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); + Assert.Equal(1, configs.Length); + + UrlDir.UrlConfig url = configs[0]; + Assert.Equal("SOME_NODE", url.type); + ConfigNode newNode = url.config; + Assert.Equal("SOME_NODE", newNode.name); + + Assert.Equal(1, newNode.values.Count); + Assert.Equal(1, newNode.nodes.Count); + + Assert.Equal("aa", newNode.values[0].name); + Assert.Equal("00", newNode.values[0].value); + + Assert.Equal("INNER_NODE_1", newNode.nodes[0].name); + + Assert.Equal("cc", newNode.nodes[0].values[0].name); + Assert.Equal("02", newNode.nodes[0].values[0].value); + + progress.Received().NeedsUnsatisfiedRoot(config2); + progress.Received().NeedsUnsatisfiedValue(url, Arg.Is(stack => stack.GetPath() == "SOME_NODE"), "bb:NEEDS[mod3]"); + progress.Received().NeedsUnsatisfiedNode(url, Arg.Is(stack => stack.GetPath() == "SOME_NODE/INNER_NODE_2:NEEDS[mod3]")); + } + [Fact] public void TestCheckNeeds__Exception() { From 58e1ca29a64494d72a06e6b4dffa300f9a79f349 Mon Sep 17 00:00:00 2001 From: sarbian Date: Wed, 6 Dec 2017 11:47:01 +0100 Subject: [PATCH 173/342] v3.0.1 for KSP 1.3.1 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 26dbe0ee..b40335a6 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.0")] +[assembly: AssemblyVersion("3.0.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 0d4d22033fd9c12c3a446cb2274aafef7c64edde Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Thu, 21 Dec 2017 17:09:10 -0800 Subject: [PATCH 174/342] Allow 0 or many spaces before operator --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index bcaeb987..9e618bc9 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -711,7 +711,7 @@ private static void PurgeUnused() #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*]+(?:,[^*\d][\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s([+\-*/^!]))?"); + private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*]+(?:,[^*\d][\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s*([+\-*/^!]))?"); // Path is group 1, operator is group 5 private static Regex parseAssign = new Regex(@"(.*)(?:\s)+([+\-*/^!])?"); From b4a2f10099e58256443c8735cd9a8551ee5b4852 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 7 Feb 2018 22:03:48 -0800 Subject: [PATCH 175/342] Update some packages --- ModuleManagerTests/ModuleManagerTests.csproj | 6 ++++-- ModuleManagerTests/packages.config | 4 ++-- TestUtilsTests/TestUtilsTests.csproj | 6 ++++-- TestUtilsTests/packages.config | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index d3ee3803..4a4c1e17 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -1,6 +1,7 @@  - + + Debug @@ -93,6 +94,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index ec095fab..dc40245d 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -2,6 +2,6 @@ - - + + \ No newline at end of file diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 93310818..1e7d65dd 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -1,6 +1,7 @@  - + + Debug @@ -68,6 +69,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/TestUtilsTests/packages.config b/TestUtilsTests/packages.config index 9139e82b..cf3669ce 100644 --- a/TestUtilsTests/packages.config +++ b/TestUtilsTests/packages.config @@ -1,6 +1,6 @@  - - + + \ No newline at end of file From 61b1c4588736198a568a75685d74733868f94899 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 7 Feb 2018 22:06:07 -0800 Subject: [PATCH 176/342] Fix :NEEDS causing nodes to be reodered Fixes #90 --- ModuleManager/NeedsChecker.cs | 37 +++++++++++++++++++------- ModuleManagerTests/NeedsCheckerTest.cs | 25 +++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs index c0078c87..386da6bb 100644 --- a/ModuleManager/NeedsChecker.cs +++ b/ModuleManager/NeedsChecker.cs @@ -23,29 +23,48 @@ public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, " has config.name == null"); } + UrlDir.UrlConfig newMod; + if (currentMod.type.IndexOf(":NEEDS[", StringComparison.OrdinalIgnoreCase) >= 0) { - mod.parent.configs.Remove(currentMod); string type = currentMod.type; - if (!CheckNeeds(ref type, mods)) + if (CheckNeeds(ref type, mods)) + { + + ConfigNode copy = new ConfigNode(type); + copy.ShallowCopyFrom(currentMod.config); + int index = mod.parent.configs.IndexOf(currentMod); + newMod = new UrlDir.UrlConfig(currentMod.parent, copy); + mod.parent.configs[index] = newMod; + } + else { progress.NeedsUnsatisfiedRoot(currentMod); + mod.parent.configs.Remove(currentMod); continue; } - - ConfigNode copy = new ConfigNode(type); - copy.ShallowCopyFrom(currentMod.config); - currentMod = new UrlDir.UrlConfig(currentMod.parent, copy); - mod.parent.configs.Add(currentMod); + } + else + { + newMod = currentMod; } // Recursively check the contents - PatchContext context = new PatchContext(currentMod, gameDatabaseRoot, logger, progress); - CheckNeeds(new NodeStack(currentMod.config), context, mods); + PatchContext context = new PatchContext(newMod, gameDatabaseRoot, logger, progress); + CheckNeeds(new NodeStack(newMod.config), context, mods); } catch (Exception ex) { + try + { + mod.parent.configs.Remove(currentMod); + } + catch(Exception ex2) + { + logger.Exception("Exception while attempting to ensure config removed" ,ex2); + } + try { progress.Exception(mod, "Exception while checking needs on root node :\n" + mod.PrettyPrint(), ex); diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index ee7b9950..a9397a55 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -196,6 +196,31 @@ public void TestCheckNeeds__Root__CaseInsensitive() } } + [Fact] + public void TestCheckNeeds__Root__KeepsOrder() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new ConfigNode("NODE_1"), file); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("NODE_2:NEEDS[mod1]"), file); + UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new ConfigNode("NODE_3:NEEDS[mod2]"), file); + UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new ConfigNode("NODE_4"), file); + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); + Assert.Equal(4, configs.Length); + + Assert.Same(config1, configs[0]); + AssertUrlCorrect("NODE_2", config2, configs[1]); + AssertUrlCorrect("NODE_3", config3, configs[2]); + Assert.Same(config4, configs[3]); + } + [Fact] public void TestCheckNeeds__Nested() { From f70701e0abc339c2236bae1e403c2722353a27ab Mon Sep 17 00:00:00 2001 From: Sarbian Date: Thu, 8 Feb 2018 09:23:52 +0100 Subject: [PATCH 177/342] v3.0.2 for KSP 1.3.1 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index b40335a6..6f791ca9 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.1")] +[assembly: AssemblyVersion("3.0.2")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ab2a0242f4ee844b0fcd9e4fe08eee8c9f63f65f Mon Sep 17 00:00:00 2001 From: Sarbian Date: Fri, 9 Feb 2018 14:54:39 +0100 Subject: [PATCH 178/342] Fix for #92 - non-US Linux decimal separator fix --- ModuleManager/MMPatchLoader.cs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 9e618bc9..5aad57cf 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -757,28 +758,29 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (assignMatch.Groups[2].Success) { - if (double.TryParse(modVal.value, out double s) && double.TryParse(val.value, out double os)) + if (double.TryParse(modVal.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s) + && double.TryParse(val.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double os)) { switch (assignMatch.Groups[2].Value[0]) { case '*': - val.value = (os * s).ToString(); + val.value = (os * s).ToString(CultureInfo.InvariantCulture); break; case '/': - val.value = (os / s).ToString(); + val.value = (os / s).ToString(CultureInfo.InvariantCulture); break; case '+': - val.value = (os + s).ToString(); + val.value = (os + s).ToString(CultureInfo.InvariantCulture); break; case '-': - val.value = (os - s).ToString(); + val.value = (os - s).ToString(CultureInfo.InvariantCulture); break; case '!': - val.value = Math.Pow(os, s).ToString(); + val.value = Math.Pow(os, s).ToString(CultureInfo.InvariantCulture); break; } } @@ -1636,28 +1638,29 @@ private static string FindAndReplaceValue( return null; } } - else if (double.TryParse(value, out double s) && double.TryParse(oValue, out double os)) + else if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s) + && double.TryParse(oValue, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double os)) { switch (op) { case '*': - value = (os * s).ToString(); + value = (os * s).ToString(CultureInfo.InvariantCulture); break; case '/': - value = (os / s).ToString(); + value = (os / s).ToString(CultureInfo.InvariantCulture); break; case '+': - value = (os + s).ToString(); + value = (os + s).ToString(CultureInfo.InvariantCulture); break; case '-': - value = (os - s).ToString(); + value = (os - s).ToString(CultureInfo.InvariantCulture); break; case '!': - value = Math.Pow(os, s).ToString(); + value = Math.Pow(os, s).ToString(CultureInfo.InvariantCulture); break; } } @@ -1805,7 +1808,7 @@ public static bool WildcardMatchValues(ConfigNode node, string type, string valu { double val = 0; bool compare = value.Length > 1 && (value[0] == '<' || value[0] == '>'); - compare = compare && Double.TryParse(value.Substring(1), out val); + compare = compare && double.TryParse(value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out val); string[] values = node.GetValues(type); for (int i = 0; i < values.Length; i++) @@ -1813,7 +1816,7 @@ public static bool WildcardMatchValues(ConfigNode node, string type, string valu if (!compare && WildcardMatch(values[i], value)) return true; - if (compare && Double.TryParse(values[i], out double val2) + if (compare && double.TryParse(values[i], NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double val2) && ((value[0] == '<' && val2 < val) || (value[0] == '>' && val2 > val))) { return true; From 94e57a9f31b44e3c5de3ac7fb1fc25dce410a53f Mon Sep 17 00:00:00 2001 From: Sarbian Date: Fri, 9 Feb 2018 14:55:03 +0100 Subject: [PATCH 179/342] v3.0.3 for KSP 1.3.1 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 6f791ca9..54243946 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.2")] +[assembly: AssemblyVersion("3.0.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 7f23ad8f988e8a8eb1208d9b06326fdb1acca812 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 10 Feb 2018 21:43:55 -0800 Subject: [PATCH 180/342] Allow operator-like character in value name Means that the operator needs a space before it in most cases Already was the case with - and * but now also the case for other operator-like characters + / ^ ! --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 5aad57cf..1542133f 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -712,7 +712,7 @@ private static void PurgeUnused() #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*]+(?:,[^*\d][\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s*([+\-*/^!]))?"); + private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!]+(?:,[^*\d][\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s*([+\-*/^!]))?"); // Path is group 1, operator is group 5 private static Regex parseAssign = new Regex(@"(.*)(?:\s)+([+\-*/^!])?"); From 7dff915b4fc4704641319d15ab23d514de696c14 Mon Sep 17 00:00:00 2001 From: sarbian Date: Sun, 11 Feb 2018 09:51:53 +0100 Subject: [PATCH 181/342] v3.0.4 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 54243946..6e0e654e 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.3")] +[assembly: AssemblyVersion("3.0.4")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 07547be2571c6197575ec989e024c174dd09dfbc Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 11 Feb 2018 15:12:19 -0800 Subject: [PATCH 182/342] Fix :NEEDS clause sometimes not getting removed Fixes #94 --- ModuleManager/NeedsChecker.cs | 14 ++++------- ModuleManagerTests/NeedsCheckerTest.cs | 32 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs index 386da6bb..e089fdbd 100644 --- a/ModuleManager/NeedsChecker.cs +++ b/ModuleManager/NeedsChecker.cs @@ -79,9 +79,7 @@ public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods) { - bool needsCopy = false; ConfigNode original = stack.value; - ConfigNode copy = new ConfigNode(original.name); for (int i = 0; i < original.values.Count; ++i) { ConfigNode.Value val = original.values[i]; @@ -90,11 +88,12 @@ private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerabl { if (CheckNeeds(ref valname, mods)) { - copy.AddValue(valname, val.value); + val.name = valname; } else { - needsCopy = true; + original.values.Remove(val); + i--; context.progress.NeedsUnsatisfiedValue(context.patchUrl, stack, val.name); } } @@ -127,11 +126,11 @@ private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerabl { node.name = nodeName; CheckNeeds(stack.Push(node), context, mods); - copy.AddNode(node); } else { - needsCopy = true; + original.nodes.Remove(node); + i--; context.progress.NeedsUnsatisfiedNode(context.patchUrl, stack.Push(node)); } } @@ -146,9 +145,6 @@ private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerabl throw; } } - - if (needsCopy) - original.ShallowCopyFrom(copy); } /// diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index a9397a55..ebd4f700 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -477,6 +477,38 @@ public void TestCheckNeeds__ExceptionWhileLoggingException() Assert.Equal(new[] { config1, config3 }, root.AllConfigs); } + [Fact] + public void TestCheckNeeds__AllNeedsSatisfied() + { + string[] modList = { "mod1", "mod2" }; + + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("SOME_NODE") + { + { "value", "1" }, + }, file); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("@SOME_NODE") + { + { "@value", "2" }, + { "@value:NEEDS[mod1] +", "4" }, + }, file); + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + ConfigNode node = root.AllConfigs.ToArray().Last().config; + Assert.Equal("@SOME_NODE", node.name); + Assert.Equal("@value", node.values[0].name); + Assert.Equal("2", node.values[0].value); + Assert.Equal("@value +", node.values[1].name); + Assert.Equal("4", node.values[1].value); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedValue(null, null, null); + + } + private UrlDir.UrlConfig CreateConfig(string name) { ConfigNode node = new TestConfigNode(name) From 97a19680aa32608b1a0ec440adc5c7258461e79c Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Tue, 12 Dec 2017 20:36:28 -0800 Subject: [PATCH 183/342] remove ModuleManager.csproj from .gitignore It was under version control anyway --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 365ba2a7..93d42729 100644 --- a/.gitignore +++ b/.gitignore @@ -106,8 +106,6 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML - -ModuleManager.csproj ModuleManager.csproj.user ModuleManager.*.dll From 532581d39680f597e2dd19d46a6af8e4a0c78dc9 Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Tue, 12 Dec 2017 22:50:26 -0800 Subject: [PATCH 184/342] Make post build into a shell script, allowing cross-platform compatibility Just requires sh to be installed on Windows. Relies on $PDB2MDB to find the pdb2mdb executable, and $KSPDIR to find where to copy to. If either of these are absent, it will be ignored (but the build will still work) --- ModuleManager/ModuleManager.csproj | 7 +---- ModuleManager/copy_build.sh | 45 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 ModuleManager/copy_build.sh diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 174ded44..5dc3f1db 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -136,11 +136,6 @@ - echo copying to "%25KSPDIR%25\GameData\" -"C:\Games\Tools\pdb2mdb\pdb2mdb.exe" $(TargetFileName) -xcopy /Y "$(TargetPath)" "C:\Games\ksp-win_dev\GameData\" -xcopy /Y "$(TargetDir)$(TargetName).pdb" "C:\Games\ksp-win_dev\GameData\" -xcopy /Y "$(TargetDir)$(TargetName).dll.mdb" "C:\Games\ksp-win_dev\GameData\" -del "C:\Games\ksp-win_dev\GameData\ModuleManager.ConfigCache" + sh -c "TARGET_PATH='$(TargetPath)' TARGET_DIR='$(TargetDir)' TARGET_NAME='$(TargetName)' sh '$(ProjectDir)/copy_build.sh'" \ No newline at end of file diff --git a/ModuleManager/copy_build.sh b/ModuleManager/copy_build.sh new file mode 100644 index 00000000..8d9f1ad5 --- /dev/null +++ b/ModuleManager/copy_build.sh @@ -0,0 +1,45 @@ +if [ -z "${TARGET_PATH}" ] ; then + echo 'Expected $TARGET_PATH to be defined but it is not' >&2 + exit 1 +elif ! [ -f "${TARGET_PATH}" ] ; then + echo 'Expected $TARGET_PATH to be a file but it is not' >&2 + exit 1 +fi + +if [ -z "${TARGET_DIR}" ] ; then + echo 'Expected $TARGET_DIR to be defined but it is not' >&2 + exit 1 +elif ! [ -d "${TARGET_DIR}" ] ; then + echo 'Expected $TARGET_DIR to be a directory but it is not' >&2 + exit 1 +fi + +if [ -z "${TARGET_NAME}" ] ; then + echo 'Expected $TARGET_NAME to be defined but it is not' >&2 + exit 1 +fi + +if [ -z "${PDB2MDB}" ] ; then + echo '$PDB2MDB not found' +else + echo "Running '${PDB2MDB}'" + "${PDB2MDB}" "${TARGET_PATH}" +fi + +if [ -z "${KSPDIR}" ] ; then + echo '$KSPDIR not found' +else + if ! [ -d "${KSPDIR}" ] ; then + echo 'Expected $KSPDIR to point to a directory but it is not' >&2 + exit 1 + fi + if ! [ -d "${KSPDIR}/GameData" ] ; then + echo 'Expected $KSPDIR to contain a GameData subdirectory but it does not' >&2 + exit 1 + fi + echo "Copying to '${KSPDIR}'" + cp "${TARGET_PATH}" "${KSPDIR}/GameData/" + test -f "${TARGET_DIR}/${TARGET_NAME}.pdb" && cp "${TARGET_DIR}/${TARGET_NAME}.pdb" "${KSPDIR}/GameData/" + test -f "${TARGET_DIR}/${TARGET_NAME}.dll.mdb" && cp "${TARGET_DIR}/${TARGET_NAME}.dll.mdb" "${KSPDIR}/GameData/" + rm -f "${KSPDIR}/GameData/ModuleManager.ConfigCache" +fi From a73b85e8149b0b57817af64fe3798bd56d84dca4 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 7 Mar 2018 22:01:38 +0100 Subject: [PATCH 185/342] v3.0.4 - KSP 1.4 require a recompile --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 54243946..6e0e654e 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.3")] +[assembly: AssemblyVersion("3.0.4")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From b8476b845b214eb6746368a78109b43363503212 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 7 Mar 2018 22:03:45 +0100 Subject: [PATCH 186/342] v3.0.5 because I am blind --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 6e0e654e..68efa231 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.4")] +[assembly: AssemblyVersion("3.0.5")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 3c4b8c3c75c0f27edaea7c75c93a2e1b2ff7227b Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 14 Mar 2018 00:53:11 +0100 Subject: [PATCH 187/342] Cats trails fix and better text position --- ModuleManager/Cats/CatManager.cs | 4 ++- ModuleManager/Cats/CatMover.cs | 4 ++- ModuleManager/Cats/CatOrbiter.cs | 4 ++- ModuleManager/ModuleManager.cs | 46 +++++++++++++++++++++++--------- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/ModuleManager/Cats/CatManager.cs b/ModuleManager/Cats/CatManager.cs index 4ef37e45..0567de54 100644 --- a/ModuleManager/Cats/CatManager.cs +++ b/ModuleManager/Cats/CatManager.cs @@ -100,8 +100,10 @@ private static GameObject LaunchCat(int scale) trail.material.mainTexture = rainbow; trail.time = 1.5f; trail.startWidth = 0.6f * scale * rainbow.height; - trail.endWidth = 0.6f * scale * rainbow.height * 0.9f; + trail.endColor = Color.white.A(0.1f); + trail.colorGradient = new Gradient {alphaKeys = new GradientAlphaKey[3] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 0.75f), new GradientAlphaKey(0.2f, 1) }}; + trail.Clear(); cat.layer = LayerMask.NameToLayer("UI"); catAnimator.frames = catFrames; diff --git a/ModuleManager/Cats/CatMover.cs b/ModuleManager/Cats/CatMover.cs index c6516243..91987e22 100644 --- a/ModuleManager/Cats/CatMover.cs +++ b/ModuleManager/Cats/CatMover.cs @@ -21,13 +21,14 @@ public class CatMover : MonoBehaviour private float time = 5; private float trailTime = 0.5f; + private int frames = 1; + // Use this for initialization void Start() { trail = this.GetComponent(); trail.sortingOrder = 2; - spriteRenderer = this.GetComponent(); offsetY = Mathf.FloorToInt(0.2f * Screen.height); @@ -38,6 +39,7 @@ void Start() totalLenth = (int) (Screen.width / time * trail.time) + 150; trail.time = trailTime; + trail.widthCurve = new AnimationCurve(new Keyframe(0, trail.startWidth ), new Keyframe(0.7f, trail.startWidth), new Keyframe(1, trail.startWidth * 0.9f)); } void Update() diff --git a/ModuleManager/Cats/CatOrbiter.cs b/ModuleManager/Cats/CatOrbiter.cs index 5c20e705..17621c6f 100644 --- a/ModuleManager/Cats/CatOrbiter.cs +++ b/ModuleManager/Cats/CatOrbiter.cs @@ -72,8 +72,10 @@ public void Init(CatOrbiter parent, float soi) transform.localScale *= scale; TrailRenderer trail = gameObject.GetComponent(); + trail.colorGradient = new Gradient() {alphaKeys = new GradientAlphaKey[3] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 0.7f), new GradientAlphaKey(0, 1) }}; trail.startWidth *= scale; - trail.endWidth *= scale; + //trail.endWidth *= scale; + trail.widthCurve = new AnimationCurve(new Keyframe(0, trail.startWidth ), new Keyframe(0.7f, trail.startWidth), new Keyframe(1, trail.startWidth * 0.9f)); //Mass = factor * 2E16; diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index a3931c78..96a40656 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -24,6 +24,7 @@ public class ModuleManager : MonoBehaviour public bool showUI = false; private Rect windowPos = new Rect(80f, 60f, 240f, 40f); + private float textPos = 0; private string version = ""; @@ -63,7 +64,24 @@ internal void Awake() // Allow loading the background in the laoding screen Application.runInBackground = true; + QualitySettings.vSyncCount = 0; + Application.targetFrameRate = -1; + + // More cool loading screen. Less 4 stoke logo. + for (int i = 0; i < LoadingScreen.Instance.Screens.Count; i++) + { + var state = LoadingScreen.Instance.Screens[i]; + state.fadeInTime = i < 3 ? 0.1f : 1; + state.displayTime = i < 3 ? 1 : 3; + state.fadeOutTime = i < 3 ? 0.1f : 1; + } + TextMeshProUGUI[] texts = LoadingScreen.Instance.gameObject.GetComponentsInChildren(); + foreach (var text in texts) + { + textPos = Mathf.Min(textPos, text.rectTransform.localPosition.y); + } + // Ensure that only one copy of the service is run per scene change. if (loadedInScene || !ElectionAndCheck()) { @@ -152,12 +170,15 @@ private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) GameObject statusGameObject = new GameObject(name); TextMeshProUGUI text = statusGameObject.AddComponent(); text.text = "STATUS"; - text.fontSize = 16; + text.fontSize = 18; text.autoSizeTextContainer = true; text.font = Resources.Load("Fonts/Calibri SDF", typeof(TMP_FontAsset)) as TMP_FontAsset; text.alignment = TextAlignmentOptions.Center; text.enableWordWrapping = false; text.isOverlay = true; + text.rectTransform.anchorMin = new Vector2(0.5f, 0); + text.rectTransform.anchorMax = new Vector2(0.5f, 0); + text.rectTransform.anchoredPosition = Vector2.zero; statusGameObject.transform.SetParent(canvas.transform); return text; @@ -224,32 +245,33 @@ internal void Update() Log("Total loading Time = " + ((float)totalTime.ElapsedMilliseconds / 1000).ToString("F3") + "s"); Application.runInBackground = GameSettings.SIMULATE_IN_BACKGROUND; + QualitySettings.vSyncCount = GameSettings.SYNC_VBL; + Application.targetFrameRate = GameSettings.FRAMERATE_LIMIT; } - float offsetY = Mathf.FloorToInt(0.23f * Screen.height); + float offsetY = textPos; float h; if (warning) { - warning.transform.localPosition = new Vector3(0, -offsetY); - h = warning.textBounds.size.y; - if (h > 0) - offsetY = offsetY + h + 10; + h = warning.text.Length > 0 ? warning.textBounds.size.y : 0; + offsetY = offsetY + h; + warning.rectTransform.localPosition = new Vector3(0, offsetY); } if (status) { - status.transform.localPosition = new Vector3(0, -offsetY); status.text = MMPatchLoader.Instance.status; - - h = status.textBounds.size.y; - if (h > 0) - offsetY = offsetY + h + 10; + h = status.text.Length > 0 ? status.textBounds.size.y: 0; + offsetY = offsetY + h; + status.transform.localPosition = new Vector3(0, offsetY); } if (errors) { - errors.transform.localPosition = new Vector3(0, -offsetY); errors.text = MMPatchLoader.Instance.errors; + h = errors.text.Length > 0 ? errors.textBounds.size.y: 0; + offsetY = offsetY + h; + errors.transform.localPosition = new Vector3(0, offsetY); } if (reloading) From 1d8d97cf2be8c7e84cbc79c0012959973683943c Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 14 Mar 2018 00:53:20 +0100 Subject: [PATCH 188/342] v3.0.6 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 68efa231..d97c7d40 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.5")] +[assembly: AssemblyVersion("3.0.6")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ef917abe1462d2e52e4114c04971451f5eacc5b6 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 16 Mar 2018 22:22:00 -0700 Subject: [PATCH 189/342] Fix deprecation --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 1542133f..979df2bc 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -88,7 +88,7 @@ private void Awake() partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); - logger = new ModLogger("ModuleManager", Debug.logger); + logger = new ModLogger("ModuleManager", Debug.unityLogger); } private bool ready; From 30fd490b1ff5ff505070a9d2a34992f622fddb8a Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 17 Mar 2018 14:12:32 -0700 Subject: [PATCH 190/342] Turn some semi-redundant methods into extensions Keeps having to reimplement them for every IBasicLogger implementation --- .../Extensions/IBasicLoggerExtensions.cs | 13 ++++++ ModuleManager/Logging/IBasicLogger.cs | 3 -- ModuleManager/Logging/ModLogger.cs | 7 +-- ModuleManager/Logging/QueueLogger.cs | 3 -- ModuleManager/ModuleManager.csproj | 1 + .../Extensions/IBasicLoggerExtensionsTest.cs | 40 +++++++++++++++++ ModuleManagerTests/Logging/ModLoggerTest.cs | 21 +++------ ModuleManagerTests/Logging/QueueLoggerTest.cs | 19 +++----- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PatchApplierTest.cs | 23 +++++----- .../Progress/PatchProgressTest.cs | 45 ++++++++++--------- 11 files changed, 104 insertions(+), 72 deletions(-) create mode 100644 ModuleManager/Extensions/IBasicLoggerExtensions.cs create mode 100644 ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs diff --git a/ModuleManager/Extensions/IBasicLoggerExtensions.cs b/ModuleManager/Extensions/IBasicLoggerExtensions.cs new file mode 100644 index 00000000..46c4c5a4 --- /dev/null +++ b/ModuleManager/Extensions/IBasicLoggerExtensions.cs @@ -0,0 +1,13 @@ +using System; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManager.Extensions +{ + public static class IBasicLoggerExtensions + { + public static void Info(this IBasicLogger logger, string message) => logger.Log(LogType.Log, message); + public static void Warning(this IBasicLogger logger, string message) => logger.Log(LogType.Warning, message); + public static void Error(this IBasicLogger logger, string message) => logger.Log(LogType.Error, message); + } +} diff --git a/ModuleManager/Logging/IBasicLogger.cs b/ModuleManager/Logging/IBasicLogger.cs index 77cfbdf4..c2fdd85e 100644 --- a/ModuleManager/Logging/IBasicLogger.cs +++ b/ModuleManager/Logging/IBasicLogger.cs @@ -7,9 +7,6 @@ namespace ModuleManager.Logging public interface IBasicLogger { void Log(LogType logType, string message); - void Info(string message); - void Warning(string message); - void Error(string message); void Exception(string message, Exception exception); } } diff --git a/ModuleManager/Logging/ModLogger.cs b/ModuleManager/Logging/ModLogger.cs index 897eec33..afd5b915 100644 --- a/ModuleManager/Logging/ModLogger.cs +++ b/ModuleManager/Logging/ModLogger.cs @@ -1,5 +1,6 @@ using System; using UnityEngine; +using ModuleManager.Extensions; namespace ModuleManager.Logging { @@ -15,14 +16,10 @@ public ModLogger(string prefix, ILogger logger) } public void Log(LogType logType, string message) => logger.Log(logType, prefix + message); - - public void Info(string message) => Log(LogType.Log, message); - public void Warning(string message) => Log(LogType.Warning, message); - public void Error(string message) => Log(LogType.Error, message); public void Exception(string message, Exception exception) { - Error(message); + this.Error(message); logger.LogException(exception); } } diff --git a/ModuleManager/Logging/QueueLogger.cs b/ModuleManager/Logging/QueueLogger.cs index 9f3d595a..82d0bb8e 100644 --- a/ModuleManager/Logging/QueueLogger.cs +++ b/ModuleManager/Logging/QueueLogger.cs @@ -14,9 +14,6 @@ public QueueLogger(IMessageQueue queue) } public void Log(LogType logType, string message) => queue.Add(new NormalMessage(logType, message)); - public void Info(string message) => Log(LogType.Log, message); - public void Warning(string message) => Log(LogType.Warning, message); - public void Error(string message) => Log(LogType.Error, message); public void Exception(string message, Exception exception) => queue.Add(new ExceptionMessage(message, exception)); } } diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 5dc3f1db..3f5cf1c3 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -40,6 +40,7 @@ + diff --git a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs new file mode 100644 index 00000000..781cccb6 --- /dev/null +++ b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs @@ -0,0 +1,40 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using ModuleManager.Logging; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class IBasicLoggerExtensionsTest + { + private IBasicLogger logger; + + public IBasicLoggerExtensionsTest() + { + logger = Substitute.For(); + } + + [Fact] + public void TestInfo() + { + logger.Info("well hi there"); + logger.Received().Log(LogType.Log, "well hi there"); + } + + [Fact] + public void TestWarning() + { + logger.Warning("I'm warning you"); + logger.Received().Log(LogType.Warning, "I'm warning you"); + } + + [Fact] + public void TestError() + { + logger.Error("You have made a grave mistake"); + logger.Received().Log(LogType.Error, "You have made a grave mistake"); + } + } +} diff --git a/ModuleManagerTests/Logging/ModLoggerTest.cs b/ModuleManagerTests/Logging/ModLoggerTest.cs index 4bd48fc5..053ebe7e 100644 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ b/ModuleManagerTests/Logging/ModLoggerTest.cs @@ -16,36 +16,27 @@ public ModLoggerTest() innerLogger = Substitute.For(); logger = new ModLogger("MyMod", innerLogger); } - [Fact] - public void TestLog() - { - logger.Log(LogType.Log, "this is a log message"); - logger.Log(LogType.Error, "this is another log message"); - - innerLogger.Received().Log(LogType.Log, "[MyMod] this is a log message"); - innerLogger.Received().Log(LogType.Error, "[MyMod] this is another log message"); - } [Fact] - public void TestInfo() + public void TestLog__Info() { - logger.Info("well hi there"); + logger.Log(LogType.Log, "well hi there"); innerLogger.Received().Log(LogType.Log, "[MyMod] well hi there"); } [Fact] - public void TestWarning() + public void TestLog__Warning() { - logger.Warning("I'm warning you"); + logger.Log(LogType.Warning, "I'm warning you"); innerLogger.Received().Log(LogType.Warning, "[MyMod] I'm warning you"); } [Fact] - public void TestError() + public void TestLog__Error() { - logger.Error("You have made a grave mistake"); + logger.Log(LogType.Error, "You have made a grave mistake"); innerLogger.Received().Log(LogType.Error, "[MyMod] You have made a grave mistake"); } diff --git a/ModuleManagerTests/Logging/QueueLoggerTest.cs b/ModuleManagerTests/Logging/QueueLoggerTest.cs index 1074c659..b984f8c2 100644 --- a/ModuleManagerTests/Logging/QueueLoggerTest.cs +++ b/ModuleManagerTests/Logging/QueueLoggerTest.cs @@ -19,30 +19,23 @@ public QueueLoggerTest() } [Fact] - public void TestLog() + public void TestLog__Info() { - logger.Log(LogType.Log, "this is a log message"); - queue.Received().Add(Arg.Is(m => m.logType == LogType.Log && m.message == "this is a log message")); - } - - [Fact] - public void TestInfo() - { - logger.Info("useful information"); + logger.Log(LogType.Log, "useful information"); queue.Received().Add(Arg.Is(m => m.logType == LogType.Log && m.message == "useful information")); } [Fact] - public void TestWarning() + public void TestLog__Warning() { - logger.Warning("not to alarm you, but something might be wrong"); + logger.Log(LogType.Warning, "not to alarm you, but something might be wrong"); queue.Received().Add(Arg.Is(m => m.logType == LogType.Warning && m.message == "not to alarm you, but something might be wrong")); } [Fact] - public void TestError() + public void TestLog__Error() { - logger.Error("you broke everything"); + logger.Log(LogType.Error, "you broke everything"); queue.Received().Add(Arg.Is(m => m.logType == LogType.Error && m.message == "you broke everything")); } diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 4a4c1e17..7540a268 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -50,6 +50,7 @@ + diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index ae5d05b4..729de882 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -2,6 +2,7 @@ using System.Linq; using Xunit; using NSubstitute; +using UnityEngine; using TestUtils; using ModuleManager; using ModuleManager.Logging; @@ -753,7 +754,7 @@ public void TestApplyPatches__Loop() progress.Received(1).PatchApplied(); progress.Received(4).ApplyingUpdate(config1, patch1); - logger.Received().Info("Looping on abc/def/@PART:HAS[~aaa[>10]] to abc/def/PART"); + logger.Received().Log(LogType.Log, "Looping on abc/def/@PART:HAS[~aaa[>10]] to abc/def/PART"); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); @@ -796,14 +797,14 @@ public void TestApplyPatches__InvalidOperator() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - logger.DidNotReceiveWithAnyArgs().Error(null); + logger.DidNotReceive().Log(LogType.Error, Arg.Any()); logger.DidNotReceiveWithAnyArgs().Exception(null, null); - logger.Received().Warning("Invalid command encountered on a patch: abc/def/%PART"); - logger.Received().Warning("Invalid command encountered on a patch: abc/def/|PART"); - logger.Received().Warning("Invalid command encountered on a patch: abc/def/#PART"); - logger.Received().Warning("Invalid command encountered on a patch: abc/def/*PART"); - logger.Received().Warning("Invalid command encountered on a patch: abc/def/&PART"); + logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/%PART"); + logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/|PART"); + logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/#PART"); + logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/*PART"); + logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/&PART"); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); @@ -840,8 +841,8 @@ public void TestApplyPatches__Copy__NameNotChanged() progress.Received().Error(patch1, "Error - when applying copy abc/def/+PART to abc/def/PART - the copy needs to have a different name than the parent (use @name = xxx)"); - logger.DidNotReceiveWithAnyArgs().Warning(null); - logger.DidNotReceiveWithAnyArgs().Error(null); + logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); + logger.DidNotReceive().Log(LogType.Error, Arg.Any()); logger.DidNotReceiveWithAnyArgs().Exception(null, null); progress.Received(1).PatchApplied(); @@ -863,8 +864,8 @@ private void EnsureNoErrors() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - logger.DidNotReceiveWithAnyArgs().Warning(null); - logger.DidNotReceiveWithAnyArgs().Error(null); + logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); + logger.DidNotReceive().Log(LogType.Error, Arg.Any()); logger.DidNotReceiveWithAnyArgs().Exception(null, null); } diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index 8d273602..d94ae87e 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -1,6 +1,7 @@ using System; using Xunit; using NSubstitute; +using UnityEngine; using TestUtils; using ModuleManager.Logging; using ModuleManager.Progress; @@ -34,8 +35,8 @@ public void Test__Constructor__Nested() progress2.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.DidNotReceiveWithAnyArgs().Info(null); - logger2.Received().Info("Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); + logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); + logger2.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); } [Fact] @@ -59,11 +60,11 @@ public void TestApplyingUpdate() progress.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Info("Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); progress.ApplyingUpdate(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Info("Applying update pqr/stu/@SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying update pqr/stu/@SOME_NODE to abc/def/SOME_NODE"); } [Fact] @@ -77,11 +78,11 @@ public void TesApplyingCopy() progress.ApplyingCopy(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Info("Applying copy ghi/jkl/+SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying copy ghi/jkl/+SOME_NODE to abc/def/SOME_NODE"); progress.ApplyingCopy(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Info("Applying copy pqr/stu/+SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying copy pqr/stu/+SOME_NODE to abc/def/SOME_NODE"); } [Fact] @@ -95,11 +96,11 @@ public void TesApplyingDelete() progress.ApplyingDelete(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Info("Applying delete ghi/jkl/!SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying delete ghi/jkl/!SOME_NODE to abc/def/SOME_NODE"); progress.ApplyingDelete(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Info("Applying delete pqr/stu/!SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying delete pqr/stu/!SOME_NODE to abc/def/SOME_NODE"); } [Fact] @@ -122,11 +123,11 @@ public void TestNeedsUnsatisfiedRoot() progress.NeedsUnsatisfiedRoot(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedRoot(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); } [Fact] @@ -141,11 +142,11 @@ public void TestNeedsUnsatisfiedNode() progress.NeedsUnsatisfiedNode(config1, stack1); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting node in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting node in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedNode(config2, stack2); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting node in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting node in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); } [Fact] @@ -160,11 +161,11 @@ public void TestNeedsUnsatisfiedValue() progress.NeedsUnsatisfiedValue(config1, stack1, "some_value"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting value in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE value: some_value as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting value in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE value: some_value as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedValue(config2, stack2, "some_other_value"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting value in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE value: some_other_value as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting value in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE value: some_other_value as it can't satisfy its NEEDS"); } [Fact] @@ -177,11 +178,11 @@ public void TestNeedsUnsatisfiedBefore() progress.NeedsUnsatisfiedBefore(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE"); + logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE"); progress.NeedsUnsatisfiedBefore(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE"); + logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE"); } [Fact] @@ -194,11 +195,11 @@ public void TestNeedsUnsatisfiedFor() progress.NeedsUnsatisfiedFor(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Warning("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)"); + logger.Received().Log(LogType.Warning, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)"); progress.NeedsUnsatisfiedFor(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Warning("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)"); + logger.Received().Log(LogType.Warning, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)"); } [Fact] @@ -211,11 +212,11 @@ public void TestNeedsUnsatisfiedAfter() progress.NeedsUnsatisfiedAfter(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER"); + logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER"); progress.NeedsUnsatisfiedAfter(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Info("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); + logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); } [Fact] @@ -230,12 +231,12 @@ public void TestError() progress.Error(config1, "An error message no one is going to read"); Assert.Equal(1, progress.Counter.errors); Assert.Equal(1, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Error("An error message no one is going to read"); + logger.Received().Log(LogType.Error, "An error message no one is going to read"); progress.Error(config2, "Maybe someone will read this one"); Assert.Equal(2, progress.Counter.errors); Assert.Equal(2, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Error("Maybe someone will read this one"); + logger.Received().Log(LogType.Error, "Maybe someone will read this one"); } [Fact] From e152f673fc60ffd04f47ce01e0b1d466f66b4ee7 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 17 Mar 2018 15:05:15 -0700 Subject: [PATCH 191/342] Split up prefixing and translating logs for unity Should be separate classes. --- ModuleManager/Logging/ModLogger.cs | 15 ++--- ModuleManager/Logging/UnityLogger.cs | 24 +++++++ ModuleManager/MMPatchLoader.cs | 2 +- ModuleManager/ModuleManager.csproj | 1 + ModuleManagerTests/Logging/ModLoggerTest.cs | 42 ++++++++++-- ModuleManagerTests/Logging/UnityLoggerTest.cs | 65 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 7 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 ModuleManager/Logging/UnityLogger.cs create mode 100644 ModuleManagerTests/Logging/UnityLoggerTest.cs diff --git a/ModuleManager/Logging/ModLogger.cs b/ModuleManager/Logging/ModLogger.cs index afd5b915..eb2d3699 100644 --- a/ModuleManager/Logging/ModLogger.cs +++ b/ModuleManager/Logging/ModLogger.cs @@ -1,26 +1,21 @@ using System; using UnityEngine; -using ModuleManager.Extensions; namespace ModuleManager.Logging { public class ModLogger : IBasicLogger { private string prefix; - private ILogger logger; + private IBasicLogger logger; - public ModLogger(string prefix, ILogger logger) + public ModLogger(string prefix, IBasicLogger logger) { + if (string.IsNullOrEmpty(prefix)) throw new ArgumentNullException(nameof(prefix)); this.prefix = "[" + prefix + "] "; - this.logger = logger; + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public void Log(LogType logType, string message) => logger.Log(logType, prefix + message); - - public void Exception(string message, Exception exception) - { - this.Error(message); - logger.LogException(exception); - } + public void Exception(string message, Exception exception) => logger.Exception(prefix + message, exception); } } diff --git a/ModuleManager/Logging/UnityLogger.cs b/ModuleManager/Logging/UnityLogger.cs new file mode 100644 index 00000000..0655b807 --- /dev/null +++ b/ModuleManager/Logging/UnityLogger.cs @@ -0,0 +1,24 @@ +using System; +using UnityEngine; +using ModuleManager.Extensions; + +namespace ModuleManager.Logging +{ + public class UnityLogger : IBasicLogger + { + private ILogger logger; + + public UnityLogger(ILogger logger) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public void Log(LogType logType, string message) => logger.Log(logType, message); + + public void Exception(string message, Exception exception) + { + this.Error(message); + logger.LogException(exception); + } + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 979df2bc..80fa46f3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -88,7 +88,7 @@ private void Awake() partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); - logger = new ModLogger("ModuleManager", Debug.unityLogger); + logger = new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger)); } private bool ready; diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 3f5cf1c3..cfd736f7 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -52,6 +52,7 @@ + diff --git a/ModuleManagerTests/Logging/ModLoggerTest.cs b/ModuleManagerTests/Logging/ModLoggerTest.cs index 053ebe7e..7c4bf427 100644 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ b/ModuleManagerTests/Logging/ModLoggerTest.cs @@ -8,15 +8,48 @@ namespace ModuleManagerTests.Logging { public class ModLoggerTest { - private ILogger innerLogger; + private IBasicLogger innerLogger; private ModLogger logger; public ModLoggerTest() { - innerLogger = Substitute.For(); + innerLogger = Substitute.For(); logger = new ModLogger("MyMod", innerLogger); } + [Fact] + public void TestConstructor__PrefixNull() + { + ArgumentNullException e = Assert.Throws(delegate + { + new ModLogger(null, innerLogger); + }); + + Assert.Equal("prefix", e.ParamName); + } + + [Fact] + public void TestConstructor__PrefixBlank() + { + ArgumentNullException e = Assert.Throws(delegate + { + new ModLogger("", innerLogger); + }); + + Assert.Equal("prefix", e.ParamName); + } + + [Fact] + public void TestConstructor__LoggerNull() + { + ArgumentNullException e = Assert.Throws(delegate + { + new ModLogger("blah", null); + }); + + Assert.Equal("logger", e.ParamName); + } + [Fact] public void TestLog__Info() { @@ -46,9 +79,8 @@ public void TestException() { Exception e = new Exception(); logger.Exception("An exception was thrown", e); - - innerLogger.Received().Log(LogType.Error, "[MyMod] An exception was thrown"); - innerLogger.Received().LogException(e); + + innerLogger.Received().Exception("[MyMod] An exception was thrown", e); } } } diff --git a/ModuleManagerTests/Logging/UnityLoggerTest.cs b/ModuleManagerTests/Logging/UnityLoggerTest.cs new file mode 100644 index 00000000..b2e22d4e --- /dev/null +++ b/ModuleManagerTests/Logging/UnityLoggerTest.cs @@ -0,0 +1,65 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class UnityLoggerTest + { + private ILogger innerLogger; + private UnityLogger logger; + + public UnityLoggerTest() + { + innerLogger = Substitute.For(); + logger = new UnityLogger(innerLogger); + } + + [Fact] + public void TestConstructor__LoggerNull() + { + ArgumentNullException e = Assert.Throws(delegate + { + new UnityLogger(null); + }); + + Assert.Equal("logger", e.ParamName); + } + + [Fact] + public void TestLog__Info() + { + logger.Log(LogType.Log, "well hi there"); + + innerLogger.Received().Log(LogType.Log, "well hi there"); + } + + [Fact] + public void TestLog__Warning() + { + logger.Log(LogType.Warning, "I'm warning you"); + + innerLogger.Received().Log(LogType.Warning, "I'm warning you"); + } + + [Fact] + public void TestLog__Error() + { + logger.Log(LogType.Error, "You have made a grave mistake"); + + innerLogger.Received().Log(LogType.Error, "You have made a grave mistake"); + } + + [Fact] + public void TestException() + { + Exception e = new Exception(); + logger.Exception("An exception was thrown", e); + + innerLogger.Received().Log(LogType.Error, "An exception was thrown"); + innerLogger.Received().LogException(e); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7540a268..f2d7eab3 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -53,6 +53,7 @@ + From cc34564ef7fcb7a316ca6ce4429b8c0e4401c47c Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 29 Mar 2018 23:26:29 -0700 Subject: [PATCH 192/342] Allow parentheses in value name --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 80fa46f3..8d52f880 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -712,7 +712,7 @@ private static void PurgeUnused() #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!]+(?:,[^*\d][\w\&\-\.\?\*]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s*([+\-*/^!]))?"); + private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\)]+(?:,[^*\d][\w\&\-\.\?\*\(\)]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s*([+\-*/^!]))?"); // Path is group 1, operator is group 5 private static Regex parseAssign = new Regex(@"(.*)(?:\s)+([+\-*/^!])?"); From a0111d261d211f4731b6afaaa330584a3c381637 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 5 Apr 2018 22:49:33 -0700 Subject: [PATCH 193/342] Allow spaces in value names Addresses #107 --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 8d52f880..35b0a9f1 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -712,7 +712,7 @@ private static void PurgeUnused() #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\)]+(?:,[^*\d][\w\&\-\.\?\*\(\)]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s*([+\-*/^!]))?"); + private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s*([+\-*/^!]))?"); // Path is group 1, operator is group 5 private static Regex parseAssign = new Regex(@"(.*)(?:\s)+([+\-*/^!])?"); From ab6b5c4610295521555a860cf25af829a9ae8446 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 7 Apr 2018 23:59:18 -0700 Subject: [PATCH 194/342] Fix operators Addresses #110 Operators are now parsed like commands, removed from the regex. --- ModuleManager/MMPatchLoader.cs | 50 ++++------- ModuleManager/ModuleManager.csproj | 2 + ModuleManager/Operator.cs | 15 ++++ ModuleManager/OperatorParser.cs | 52 +++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/OperatorParserTest.cs | 92 ++++++++++++++++++++ 6 files changed, 180 insertions(+), 32 deletions(-) create mode 100644 ModuleManager/Operator.cs create mode 100644 ModuleManager/OperatorParser.cs create mode 100644 ModuleManagerTests/OperatorParserTest.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 35b0a9f1..c38938cd 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -712,10 +712,7 @@ private static void PurgeUnused() #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?(?:\s*([+\-*/^!]))?"); - - // Path is group 1, operator is group 5 - private static Regex parseAssign = new Regex(@"(.*)(?:\s)+([+\-*/^!])?"); + private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?"); // ModifyNode applies the ConfigNode mod as a 'patch' to ConfigNode original, then returns the patched ConfigNode. // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse. @@ -737,17 +734,10 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon Command cmd = CommandParser.Parse(modVal.name, out string valName); + Operator op = OperatorParser.Parse(valName, out valName); + if (cmd == Command.Special) { - Match assignMatch = parseAssign.Match(valName); - if (!assignMatch.Success) - { - context.progress.Error(context.patchUrl, "Error - Cannot parse value assigning command: " + valName); - continue; - } - - valName = assignMatch.Groups[1].Value; - ConfigNode.Value val = RecurseVariableSearch(valName, nodeStack.Push(mod), context); if (val == null) @@ -756,30 +746,30 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon continue; } - if (assignMatch.Groups[2].Success) + if (op != Operator.Assign) { if (double.TryParse(modVal.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s) && double.TryParse(val.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double os)) { - switch (assignMatch.Groups[2].Value[0]) + switch (op) { - case '*': + case Operator.Multiply: val.value = (os * s).ToString(CultureInfo.InvariantCulture); break; - case '/': + case Operator.Divide: val.value = (os / s).ToString(CultureInfo.InvariantCulture); break; - case '+': + case Operator.Add: val.value = (os + s).ToString(CultureInfo.InvariantCulture); break; - case '-': + case Operator.Subtract: val.value = (os - s).ToString(CultureInfo.InvariantCulture); break; - case '!': + case Operator.Exponentiate: val.value = Math.Pow(os, s).ToString(CultureInfo.InvariantCulture); break; } @@ -841,10 +831,6 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (newNode.values[i].name == valName) valCount++; - char op = ' '; - if (match.Groups[5].Success) - op = match.Groups[5].Value[0]; - string varValue; switch (cmd) { @@ -1582,7 +1568,7 @@ private static string FindAndReplaceValue( ref string valName, string value, ConfigNode newNode, - char op, + Operator op, int index, out ConfigNode.Value origVal, PatchContext context, @@ -1611,9 +1597,9 @@ private static string FindAndReplaceValue( { value = backupValue; oValue = strArray[posIndex]; - if (op != ' ') + if (op != Operator.Assign) { - if (op == '^') + if (op == Operator.RegexReplace) { try { @@ -1643,23 +1629,23 @@ private static string FindAndReplaceValue( { switch (op) { - case '*': + case Operator.Multiply: value = (os * s).ToString(CultureInfo.InvariantCulture); break; - case '/': + case Operator.Divide: value = (os / s).ToString(CultureInfo.InvariantCulture); break; - case '+': + case Operator.Add: value = (os + s).ToString(CultureInfo.InvariantCulture); break; - case '-': + case Operator.Subtract: value = (os - s).ToString(CultureInfo.InvariantCulture); break; - case '!': + case Operator.Exponentiate: value = Math.Pow(os, s).ToString(CultureInfo.InvariantCulture); break; } diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index cfd736f7..61344d57 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -57,6 +57,8 @@ + + diff --git a/ModuleManager/Operator.cs b/ModuleManager/Operator.cs new file mode 100644 index 00000000..2846efa6 --- /dev/null +++ b/ModuleManager/Operator.cs @@ -0,0 +1,15 @@ +using System; + +namespace ModuleManager +{ + public enum Operator + { + Assign, + Add, + Subtract, + Multiply, + Divide, + Exponentiate, + RegexReplace, + } +} diff --git a/ModuleManager/OperatorParser.cs b/ModuleManager/OperatorParser.cs new file mode 100644 index 00000000..c122bca0 --- /dev/null +++ b/ModuleManager/OperatorParser.cs @@ -0,0 +1,52 @@ +using System; + +namespace ModuleManager +{ + public static class OperatorParser + { + public static Operator Parse(string name, out string valueName) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + if (name.Length == 0) + { + valueName = string.Empty; + return Operator.Assign; + } + + Operator ret; + switch (name[name.Length - 1]) + { + case '+': + ret = Operator.Add; + break; + + case '-': + ret = Operator.Subtract; + break; + + case '*': + ret = Operator.Multiply; + break; + + case '/': + ret = Operator.Divide; + break; + + case '!': + ret = Operator.Exponentiate; + break; + + case '^': + ret = Operator.RegexReplace; + break; + + default: + valueName = name; + return Operator.Assign; + } + valueName = name.Substring(0, name.Length - 1).TrimEnd(); + return ret; + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index f2d7eab3..0710dfc8 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -59,6 +59,7 @@ + diff --git a/ModuleManagerTests/OperatorParserTest.cs b/ModuleManagerTests/OperatorParserTest.cs new file mode 100644 index 00000000..95135f02 --- /dev/null +++ b/ModuleManagerTests/OperatorParserTest.cs @@ -0,0 +1,92 @@ +using System; +using Xunit; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class OperatorParserTest + { + [Fact] + public void TestParse__Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + OperatorParser.Parse(null, out string _); + }); + + Assert.Equal("name", ex.ParamName); + } + + [Fact] + public void TestParse__Empty() + { + Operator op = OperatorParser.Parse("", out string result); + + Assert.Equal(Operator.Assign, op); + Assert.Equal("", result); + } + + [Fact] + public void TestParse__Assign() + { + Operator op = OperatorParser.Parse("some_stuff,1[2, ]", out string result); + + Assert.Equal(Operator.Assign, op); + Assert.Equal("some_stuff,1[2, ]", result); + } + + [Fact] + public void TestParse__Add() + { + Operator op = OperatorParser.Parse("some_stuff,1[2, ] +", out string result); + + Assert.Equal(Operator.Add, op); + Assert.Equal("some_stuff,1[2, ]", result); + } + + [Fact] + public void TestParse__Subtract() + { + Operator op = OperatorParser.Parse("some_stuff,1[2, ] -", out string result); + + Assert.Equal(Operator.Subtract, op); + Assert.Equal("some_stuff,1[2, ]", result); + } + + [Fact] + public void TestParse__Multiply() + { + Operator op = OperatorParser.Parse("some_stuff,1[2, ] *", out string result); + + Assert.Equal(Operator.Multiply, op); + Assert.Equal("some_stuff,1[2, ]", result); + } + + [Fact] + public void TestParse__Divide() + { + Operator op = OperatorParser.Parse("some_stuff,1[2, ] /", out string result); + + Assert.Equal(Operator.Divide, op); + Assert.Equal("some_stuff,1[2, ]", result); + } + + [Fact] + public void TestParse__Exponentiate() + { + Operator op = OperatorParser.Parse("some_stuff,1[2, ] !", out string result); + + Assert.Equal(Operator.Exponentiate, op); + Assert.Equal("some_stuff,1[2, ]", result); + } + + [Fact] + public void TestParse__RegexReplace() + { + Operator op = OperatorParser.Parse("some_stuff,1[2, ] ^", out string result); + + Assert.Equal(Operator.RegexReplace, op); + Assert.Equal("some_stuff,1[2, ]", result); + } + } +} From c28744139c01a32caf2c98e8749ff9ba5276c3d5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 19 Apr 2018 23:40:09 -0700 Subject: [PATCH 195/342] Fix value assignment with * indexer Broken in #111 - probably an unusual case but it would have worked before. Added tests to ensure that this fixes it. Tests are not and will probably never cover all of MMPatchLoader.ModifyNode but useful to add bugfix cases here as they occur. --- ModuleManager/MMPatchLoader.cs | 8 +- ModuleManagerTests/MMPatchLoaderTest.cs | 90 ++++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 ModuleManagerTests/MMPatchLoaderTest.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index c38938cd..5be472a8 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -733,8 +733,12 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon #endif Command cmd = CommandParser.Parse(modVal.name, out string valName); - - Operator op = OperatorParser.Parse(valName, out valName); + + Operator op; + if (valName.Length > 2 && valName[valName.Length - 2] == ',') + op = Operator.Assign; + else + op = OperatorParser.Parse(valName, out valName); if (cmd == Command.Special) { diff --git a/ModuleManagerTests/MMPatchLoaderTest.cs b/ModuleManagerTests/MMPatchLoaderTest.cs new file mode 100644 index 00000000..efeb39a1 --- /dev/null +++ b/ModuleManagerTests/MMPatchLoaderTest.cs @@ -0,0 +1,90 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; +using ModuleManager.Progress; +using NodeStack = ModuleManager.Collections.ImmutableStack; + +namespace ModuleManagerTests +{ + // This is not intended to fully test ModifyNode, however it is useful to include tests for bugfixes here before it is split up + public class MMPatchLoaderTest + { + private readonly IBasicLogger logger = Substitute.For(); + private readonly IPatchProgress progress = Substitute.For(); + private readonly UrlDir root = UrlBuilder.CreateRoot(); + + [Fact] + public void TestModifyNode__IndexAllWithAssign() + { + ConfigNode c1 = new TestConfigNode("NODE") + { + { "foo", "bar1" }, + { "foo", "bar2" }, + }; + + UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("@NODE") + { + { "@foo,*", "bar3" }, + }, root); + + PatchContext context = new PatchContext(c2u, root, logger, progress); + + ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context); + + EnsureNoErrors(); + + AssertConfigNodesEqual(new TestConfigNode("NODE") + { + { "foo", "bar3" }, + { "foo", "bar3" }, + }, c3); + } + + [Fact] + public void TestModifyNode__MultiplyValue() + { + ConfigNode c1 = new TestConfigNode("NODE") + { + { "foo", "3" }, + { "foo", "5" }, + }; + + UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("@NODE") + { + { "@foo *", "2" }, + }, root); + + PatchContext context = new PatchContext(c2u, root, logger, progress); + + ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context); + + EnsureNoErrors(); + + AssertConfigNodesEqual(new TestConfigNode("NODE") + { + { "foo", "6" }, + { "foo", "5" }, + }, c3); + } + + private void AssertConfigNodesEqual(ConfigNode expected, ConfigNode observed) + { + Assert.Equal(expected.ToString(), observed.ToString()); + } + + private void EnsureNoErrors() + { + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); + logger.DidNotReceive().Log(LogType.Error, Arg.Any()); + logger.DidNotReceive().Log(LogType.Exception, Arg.Any()); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 0710dfc8..1909a654 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -54,6 +54,7 @@ + From 0aa64b5f002c42400ffc3ef7f5499370f79a0313 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 20 Apr 2018 23:47:25 -0700 Subject: [PATCH 196/342] Reflection fields should be readonly --- ModuleManagerTests/NeedsCheckerTest.cs | 8 ++++---- TestUtils/UrlBuilder.cs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index ebd4f700..1307be7b 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -13,11 +13,11 @@ namespace ModuleManagerTests { public class NeedsCheckerTest { - private UrlDir root; - private UrlDir.UrlFile file; + private readonly UrlDir root; + private readonly UrlDir.UrlFile file; - private IPatchProgress progress; - private IBasicLogger logger; + private readonly IPatchProgress progress; + private readonly IBasicLogger logger; public NeedsCheckerTest() { diff --git a/TestUtils/UrlBuilder.cs b/TestUtils/UrlBuilder.cs index 7ad78fd6..eaf8192e 100644 --- a/TestUtils/UrlBuilder.cs +++ b/TestUtils/UrlBuilder.cs @@ -7,13 +7,13 @@ namespace TestUtils { public static class UrlBuilder { - private static FieldInfo UrlDir__field__name; - private static FieldInfo UrlDir__field__root; - private static FieldInfo UrlDir__field__parent; + private static readonly FieldInfo UrlDir__field__name; + private static readonly FieldInfo UrlDir__field__root; + private static readonly FieldInfo UrlDir__field__parent; - private static FieldInfo UrlFile__field__name; - private static FieldInfo UrlFile__field__fileType; - private static FieldInfo UrlFile__field__fileExtension; + private static readonly FieldInfo UrlFile__field__name; + private static readonly FieldInfo UrlFile__field__fileType; + private static readonly FieldInfo UrlFile__field__fileExtension; static UrlBuilder() { From ae2a14f9154564360f07a6ef9d41ebc5add23eb6 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 21 Apr 2018 01:09:00 -0700 Subject: [PATCH 197/342] Create special GameData subdirectory It's special --- TestUtils/UrlBuilder.cs | 26 +++++++++++++++++++++ TestUtilsTests/UrlBuilderTest.cs | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/TestUtils/UrlBuilder.cs b/TestUtils/UrlBuilder.cs index eaf8192e..2fff4a47 100644 --- a/TestUtils/UrlBuilder.cs +++ b/TestUtils/UrlBuilder.cs @@ -7,6 +7,7 @@ namespace TestUtils { public static class UrlBuilder { + private static readonly FieldInfo UrlDir__field__type; private static readonly FieldInfo UrlDir__field__name; private static readonly FieldInfo UrlDir__field__root; private static readonly FieldInfo UrlDir__field__parent; @@ -18,9 +19,11 @@ public static class UrlBuilder static UrlBuilder() { FieldInfo[] UrlDirFields = typeof(UrlDir).GetFields(BindingFlags.Instance | BindingFlags.NonPublic); + FieldInfo[] UrlDirFields__DirectoryType = UrlDirFields.Where(field => field.FieldType == typeof(UrlDir.DirectoryType)).ToArray(); FieldInfo[] UrlDirFields__string = UrlDirFields.Where(field => field.FieldType == typeof(string)).ToArray(); FieldInfo[] UrlDirFields__UrlDir = UrlDirFields.Where(field => field.FieldType == typeof(UrlDir)).ToArray(); + UrlDir__field__type = UrlDirFields__DirectoryType[0]; UrlDir__field__name = UrlDirFields__string[0]; UrlDir__field__root = UrlDirFields__UrlDir[0]; UrlDir__field__parent = UrlDirFields__UrlDir[1]; @@ -67,6 +70,29 @@ public static UrlDir CreateDir(string url, UrlDir parent = null) return current; } + public static UrlDir CreateGameData(UrlDir root = null) + { + if (root == null) + { + root = CreateRoot(); + } + else + { + UrlDir potentialGameData = root.children.FirstOrDefault(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); + if (potentialGameData != null) return potentialGameData; + } + + UrlDir gameData = CreateRoot(); + UrlDir__field__name.SetValue(gameData, ""); + UrlDir__field__type.SetValue(gameData, UrlDir.DirectoryType.GameData); + UrlDir__field__root.SetValue(gameData, root); + UrlDir__field__parent.SetValue(gameData, root); + + root.children.Add(gameData); + + return gameData; + } + public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) { int sepIndex = path.LastIndexOf('/'); diff --git a/TestUtilsTests/UrlBuilderTest.cs b/TestUtilsTests/UrlBuilderTest.cs index c0bcb9c2..956350dd 100644 --- a/TestUtilsTests/UrlBuilderTest.cs +++ b/TestUtilsTests/UrlBuilderTest.cs @@ -135,6 +135,45 @@ public void TestCreateDir__Url__AlreadyExists() Assert.Contains(dir1, root.AllDirectories); } + [Fact] + public void TestCreateGameData() + { + UrlDir gameData = UrlBuilder.CreateGameData(); + + Assert.Equal("", gameData.name); + Assert.Equal("", gameData.url); + Assert.Equal(UrlDir.DirectoryType.GameData, gameData.type); + UrlDir root = Assert.IsType(gameData.root); + Assert.Same(root, gameData.parent); + Assert.Equal("root", root.name); + Assert.Null(root.parent); + Assert.Same(root, root.root); + Assert.Contains(gameData, root.children); + } + + [Fact] + public void TestCreateGameData__SpecifyRoot() + { + UrlDir root = UrlBuilder.CreateRoot(); + UrlDir gameData = UrlBuilder.CreateGameData(root); + + Assert.Equal("", gameData.name); + Assert.Equal("", gameData.url); + Assert.Equal(UrlDir.DirectoryType.GameData, gameData.type); + Assert.Same(root, gameData.parent); + Assert.Contains(gameData, root.children); + } + + [Fact] + public void TestCreateGameData__SpecifyRoot__GameDataAlreadyExists() + { + UrlDir root = UrlBuilder.CreateRoot(); + UrlDir gameData1 = UrlBuilder.CreateGameData(root); + UrlDir gameData2 = UrlBuilder.CreateGameData(root); + + Assert.Same(gameData1, gameData2); + } + [Fact] public void TestCreateFile() { From 4a7e3c8a455084f1a05ec187819c46db66a2b1d3 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 21 Apr 2018 01:16:54 -0700 Subject: [PATCH 198/342] Allow checking needs against directories If the needs string contains a / it will check for a directory with that path in GameData. Notes: * PluginData folders are excluded * Leading and trailing slashes are allowed * Multiple slashes together will be treated as a single slash * Comaprison is case sensitive --- ModuleManager/NeedsChecker.cs | 37 +++++++++++++----- ModuleManagerTests/NeedsCheckerTest.cs | 54 +++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs index e089fdbd..792766cf 100644 --- a/ModuleManager/NeedsChecker.cs +++ b/ModuleManager/NeedsChecker.cs @@ -12,6 +12,8 @@ public static class NeedsChecker { public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, IPatchProgress progress, IBasicLogger logger) { + UrlDir gameData = gameDatabaseRoot.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); + foreach (UrlDir.UrlConfig mod in gameDatabaseRoot.AllConfigs.ToArray()) { UrlDir.UrlConfig currentMod = mod; @@ -29,7 +31,7 @@ public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, { string type = currentMod.type; - if (CheckNeeds(ref type, mods)) + if (CheckNeeds(ref type, mods, gameData)) { ConfigNode copy = new ConfigNode(type); @@ -52,7 +54,7 @@ public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, // Recursively check the contents PatchContext context = new PatchContext(newMod, gameDatabaseRoot, logger, progress); - CheckNeeds(new NodeStack(newMod.config), context, mods); + CheckNeeds(new NodeStack(newMod.config), context, mods, gameData); } catch (Exception ex) { @@ -77,7 +79,7 @@ public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, } } - private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods) + private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods, UrlDir gameData) { ConfigNode original = stack.value; for (int i = 0; i < original.values.Count; ++i) @@ -86,7 +88,7 @@ private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerabl string valname = val.name; try { - if (CheckNeeds(ref valname, mods)) + if (CheckNeeds(ref valname, mods, gameData)) { val.name = valname; } @@ -122,10 +124,10 @@ private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerabl try { - if (CheckNeeds(ref nodeName, mods)) + if (CheckNeeds(ref nodeName, mods, gameData)) { node.name = nodeName; - CheckNeeds(stack.Push(node), context, mods); + CheckNeeds(stack.Push(node), context, mods, gameData); } else { @@ -150,7 +152,7 @@ private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerabl /// /// Returns true if needs are satisfied. /// - private static bool CheckNeeds(ref string name, IEnumerable mods) + private static bool CheckNeeds(ref string name, IEnumerable mods, UrlDir gameData) { if (name == null) return true; @@ -159,7 +161,7 @@ private static bool CheckNeeds(ref string name, IEnumerable mods) if (idxStart < 0) return true; int idxEnd = name.IndexOf(']', idxStart + 7); - string needsString = name.Substring(idxStart + 7, idxEnd - idxStart - 7).ToUpper(); + string needsString = name.Substring(idxStart + 7, idxEnd - idxStart - 7); name = name.Substring(0, idxStart) + name.Substring(idxEnd + 1); @@ -174,7 +176,24 @@ private static bool CheckNeeds(ref string name, IEnumerable mods) bool not = orDependency[0] == '!'; string toFind = not ? orDependency.Substring(1) : orDependency; - bool found = mods.Contains(toFind, StringComparer.OrdinalIgnoreCase); + + bool found = mods.Contains(toFind.ToUpper(), StringComparer.OrdinalIgnoreCase); + if (!found && toFind.Contains('/')) + { + string[] splits = toFind.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + found = true; + UrlDir current = gameData; + for (int i = 0; i < splits.Length; i++) + { + current = current.children.FirstOrDefault(dir => dir.name == splits[i]); + if (current == null) + { + found = false; + break; + } + } + } if (not == !found) { diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index 1307be7b..73c2918b 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -14,6 +14,7 @@ namespace ModuleManagerTests public class NeedsCheckerTest { private readonly UrlDir root; + private readonly UrlDir gameData; private readonly UrlDir.UrlFile file; private readonly IPatchProgress progress; @@ -22,7 +23,8 @@ public class NeedsCheckerTest public NeedsCheckerTest() { root = UrlBuilder.CreateRoot(); - file = UrlBuilder.CreateFile("abc/def.cfg", root); + gameData = UrlBuilder.CreateGameData(root); + file = UrlBuilder.CreateFile("abc/def.cfg", gameData); progress = Substitute.For(); logger = Substitute.For(); @@ -453,6 +455,56 @@ public void TestCheckNeeds__Exception() Assert.Equal(new[] { config1, config3 }, root.AllConfigs); } + [Fact] + public void TestCheckNeeds__Directory() + { + string[] modList = { "mod1", "mod/2" }; + + UrlBuilder.CreateDir("ghi/jkl", gameData); + + UrlDir.UrlConfig config01 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE01:NEEDS[/abc]"), file); + UrlDir.UrlConfig config02 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE02:NEEDS[abc/]"), file); + UrlDir.UrlConfig config03 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE03:NEEDS[/abc/]"), file); + UrlDir.UrlConfig config04 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE04:NEEDS[ghi/jkl]"), file); + UrlDir.UrlConfig config05 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE05:NEEDS[/ghi/jkl]"), file); + UrlDir.UrlConfig config06 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE06:NEEDS[ghi/jkl/]"), file); + UrlDir.UrlConfig config07 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE07:NEEDS[mod1&ghi/jkl]"), file); + UrlDir.UrlConfig config08 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE08:NEEDS[mod3|ghi/jkl]"), file); + UrlDir.UrlConfig config09 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE09:NEEDS[abc/&ghi/jkl]"), file); + UrlDir.UrlConfig config10 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE10:NEEDS[mod/2]"), file); + + UrlDir.UrlConfig config11 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE11:NEEDS[abc]"), file); + UrlDir.UrlConfig config12 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE12:needs[mod3&ghi/jkl]"), file); + UrlDir.UrlConfig config13 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE13:NEEDS[Ghi/jkl]"), file); + UrlDir.UrlConfig config14 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE14:NEEDS[mno/pqr]"), file); + + NeedsChecker.CheckNeeds(root, modList, progress, logger); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + + UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); + Assert.Equal(10, configs.Length); + + AssertUrlCorrect("SOME_NODE01", config01, configs[0]); + AssertUrlCorrect("SOME_NODE02", config02, configs[1]); + AssertUrlCorrect("SOME_NODE03", config03, configs[2]); + AssertUrlCorrect("SOME_NODE04", config04, configs[3]); + AssertUrlCorrect("SOME_NODE05", config05, configs[4]); + AssertUrlCorrect("SOME_NODE06", config06, configs[5]); + AssertUrlCorrect("SOME_NODE07", config07, configs[6]); + AssertUrlCorrect("SOME_NODE08", config08, configs[7]); + AssertUrlCorrect("SOME_NODE09", config09, configs[8]); + AssertUrlCorrect("SOME_NODE09", config09, configs[8]); + AssertUrlCorrect("SOME_NODE10", config10, configs[9]); + + progress.Received().NeedsUnsatisfiedRoot(config11); + progress.Received().NeedsUnsatisfiedRoot(config12); + progress.Received().NeedsUnsatisfiedRoot(config13); + progress.Received().NeedsUnsatisfiedRoot(config14); + } + [Fact] public void TestCheckNeeds__ExceptionWhileLoggingException() { From 1b642c0d7193148d2058f5fd1d8e50fc5140314d Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Sat, 5 May 2018 02:02:29 -0700 Subject: [PATCH 199/342] Require at least one space before the operator (#119) Fixes wildcards in value names. If * appears in at the end of value name without a space it should be interpreted as a wildcard rather than the multiplication operator --- ModuleManager/OperatorParser.cs | 5 +++++ ModuleManagerTests/OperatorParserTest.cs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/ModuleManager/OperatorParser.cs b/ModuleManager/OperatorParser.cs index c122bca0..e85eebab 100644 --- a/ModuleManager/OperatorParser.cs +++ b/ModuleManager/OperatorParser.cs @@ -13,6 +13,11 @@ public static Operator Parse(string name, out string valueName) valueName = string.Empty; return Operator.Assign; } + else if (name.Length == 1 || (name[name.Length - 2] != ' ' && name[name.Length - 2] != '\t')) + { + valueName = name; + return Operator.Assign; + } Operator ret; switch (name[name.Length - 1]) diff --git a/ModuleManagerTests/OperatorParserTest.cs b/ModuleManagerTests/OperatorParserTest.cs index 95135f02..dfc35d2a 100644 --- a/ModuleManagerTests/OperatorParserTest.cs +++ b/ModuleManagerTests/OperatorParserTest.cs @@ -88,5 +88,23 @@ public void TestParse__RegexReplace() Assert.Equal(Operator.RegexReplace, op); Assert.Equal("some_stuff,1[2, ]", result); } + + [Fact] + public void TestParse__NoSpaceMeansNoOp() + { + Operator op = OperatorParser.Parse("some_stuff*", out string result); + + Assert.Equal(Operator.Assign, op); + Assert.Equal("some_stuff*", result); + } + + [Fact] + public void TestParse__SingleCharacterNotOp() + { + Operator op = OperatorParser.Parse("*", out string result); + + Assert.Equal(Operator.Assign, op); + Assert.Equal("*", result); + } } } From dc4802f4ca2a6cd112b587ab202276727047a9a0 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 5 May 2018 12:56:54 +0200 Subject: [PATCH 200/342] Fix SHA generation for DLL - Fix #120 Make sure TransformFinalBlock is called *after* the last block --- ModuleManager/MMPatchLoader.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 5be472a8..40ef2c93 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -443,11 +443,7 @@ private void IsCacheUpToDate() // hash the file content byte[] contentBytes = File.ReadAllBytes(files[i].fullPath); - if (i == files.Length - 1) - sha.TransformFinalBlock(contentBytes, 0, contentBytes.Length); - else - sha.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); - + sha.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); filesha.ComputeHash(contentBytes); if (!filesSha.ContainsKey(files[i].url)) @@ -468,6 +464,9 @@ private void IsCacheUpToDate() sha.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); } + byte[] godsFinalMessageToHisCreatiom = Encoding.UTF8.GetBytes("We apologize for the inconvenience."); + sha.TransformFinalBlock(godsFinalMessageToHisCreatiom, 0, godsFinalMessageToHisCreatiom.Length); + configSha = BitConverter.ToString(sha.Hash); sha.Clear(); filesha.Clear(); From 66443617dbb6700955d0db084d2a4f49283ec3a7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 5 May 2018 12:57:48 +0200 Subject: [PATCH 201/342] v3.0.7 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index d97c7d40..233161a6 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.6")] +[assembly: AssemblyVersion("3.0.7")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From d76b7b97af312ef4e0784b6fd238b0d89f3708bd Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 5 May 2018 17:23:00 +0200 Subject: [PATCH 202/342] Stupid typo --- ModuleManager/MMPatchLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 40ef2c93..adcf1999 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -464,8 +464,8 @@ private void IsCacheUpToDate() sha.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); } - byte[] godsFinalMessageToHisCreatiom = Encoding.UTF8.GetBytes("We apologize for the inconvenience."); - sha.TransformFinalBlock(godsFinalMessageToHisCreatiom, 0, godsFinalMessageToHisCreatiom.Length); + byte[] godsFinalMessageToHisCreation = Encoding.UTF8.GetBytes("We apologize for the inconvenience."); + sha.TransformFinalBlock(godsFinalMessageToHisCreation, 0, godsFinalMessageToHisCreation.Length); configSha = BitConverter.ToString(sha.Hash); sha.Clear(); From 4dcf1092f50b58011dd8572c20817d4602ff7736 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 8 Oct 2018 22:14:05 -0700 Subject: [PATCH 203/342] Remove unused variable --- ModuleManager/Cats/CatMover.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ModuleManager/Cats/CatMover.cs b/ModuleManager/Cats/CatMover.cs index 91987e22..bf1833a1 100644 --- a/ModuleManager/Cats/CatMover.cs +++ b/ModuleManager/Cats/CatMover.cs @@ -21,8 +21,6 @@ public class CatMover : MonoBehaviour private float time = 5; private float trailTime = 0.5f; - private int frames = 1; - // Use this for initialization void Start() { From 38cbaa10836ae50948e20989dd7354ab6b5f0763 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 8 Oct 2018 22:16:28 -0700 Subject: [PATCH 204/342] Make const stuff const --- ModuleManager/Cats/CatMover.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ModuleManager/Cats/CatMover.cs b/ModuleManager/Cats/CatMover.cs index bf1833a1..f5dd7678 100644 --- a/ModuleManager/Cats/CatMover.cs +++ b/ModuleManager/Cats/CatMover.cs @@ -18,8 +18,8 @@ public class CatMover : MonoBehaviour public float scale = 2; - private float time = 5; - private float trailTime = 0.5f; + private const float time = 5; + private const float trailTime = time / 4; // Use this for initialization void Start() @@ -32,8 +32,6 @@ void Start() offsetY = Mathf.FloorToInt(0.2f * Screen.height); spos.z = -1; - - trailTime = time / 4; totalLenth = (int) (Screen.width / time * trail.time) + 150; trail.time = trailTime; From d6edd62cb52b93e3233d4e8cd204f3f6ff3f1e39 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 8 Oct 2018 22:30:32 -0700 Subject: [PATCH 205/342] Fix typo in test --- ModuleManagerTests/PatchExtractorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 453d56e2..2a56c11a 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -186,14 +186,14 @@ public void TestSortAndExtractPatches() AssertUrlCorrect("@NADE", beforeMod2Configs[3], currentPatches[3]); currentPatches = list.modPasses["mod2"].forPatches; - Assert.Equal(forMod1Configs.Length, currentPatches.Count); + Assert.Equal(forMod2Configs.Length, currentPatches.Count); AssertUrlCorrect("@NODE", forMod2Configs[0], currentPatches[0]); AssertUrlCorrect("@NODE[foo]:HAS[#bar]", forMod2Configs[1], currentPatches[1]); AssertUrlCorrect("@NADE", forMod2Configs[2], currentPatches[2]); AssertUrlCorrect("@NADE", forMod2Configs[3], currentPatches[3]); currentPatches = list.modPasses["mod2"].afterPatches; - Assert.Equal(afterMod1Configs.Length, currentPatches.Count); + Assert.Equal(afterMod2Configs.Length, currentPatches.Count); AssertUrlCorrect("@NODE", afterMod2Configs[0], currentPatches[0]); AssertUrlCorrect("@NODE[foo]:HAS[#bar]", afterMod2Configs[1], currentPatches[1]); AssertUrlCorrect("@NADE", afterMod2Configs[2], currentPatches[2]); From e6746b440f39c04a0c320ca502aeb7bdaabd81a1 Mon Sep 17 00:00:00 2001 From: sarbian Date: Mon, 15 Oct 2018 23:37:31 +0200 Subject: [PATCH 206/342] v3.1.0 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 233161a6..a27f4b16 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.0.7")] +[assembly: AssemblyVersion("3.1.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 7ea256db1aa3c2e961d0178307fe9561f7206d0d Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 24 Mar 2018 12:20:04 -0700 Subject: [PATCH 207/342] Make patch stopwatch local It's not needed outside of ProcessPatch(), which contains everything it needs to time --- ModuleManager/MMPatchLoader.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index adcf1999..45e91e8e 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -57,8 +57,6 @@ public class MMPatchLoader : LoadingSystem private bool useCache = false; - private readonly Stopwatch patchSw = new Stopwatch(); - private static readonly List postPatchCallbacks = new List(); private const float yieldInterval = 1f/30f; // Patch at ~30fps @@ -95,12 +93,6 @@ private void Awake() public override bool IsReady() { - //return false; - if (ready) - { - patchSw.Stop(); - logger.Info("Ran in " + ((float)patchSw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); - } return ready; } @@ -113,9 +105,6 @@ public override string ProgressTitle() public override void StartLoad() { - patchSw.Reset(); - patchSw.Start(); - ready = false; // DB check used to track the now fixed TextureReplacer corruption @@ -132,6 +121,9 @@ public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) private IEnumerator ProcessPatch() { + Stopwatch patchSw = new Stopwatch(); + patchSw.Start(); + status = "Checking Cache"; logger.Info(status); yield return null; @@ -390,6 +382,9 @@ float updateTimeRemaining() yield return null; + patchSw.Stop(); + logger.Info("Ran in " + ((float)patchSw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); + ready = true; } From 8056f597afe14208b0ff9dd133cf9b8eb9b7c341 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 24 Mar 2018 12:32:11 -0700 Subject: [PATCH 208/342] Make useCache local Really only needed for a few lines in ProcessPatch() --- ModuleManager/MMPatchLoader.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 45e91e8e..c786220f 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -55,8 +55,6 @@ public class MMPatchLoader : LoadingSystem private string configSha; private Dictionary filesSha = new Dictionary(); - private bool useCache = false; - private static readonly List postPatchCallbacks = new List(); private const float yieldInterval = 1f/30f; // Patch at ~30fps @@ -127,15 +125,15 @@ private IEnumerator ProcessPatch() status = "Checking Cache"; logger.Info(status); yield return null; - + + bool useCache = false; try { - IsCacheUpToDate(); + useCache = IsCacheUpToDate(); } catch (Exception ex) { logger.Exception("Exception in IsCacheUpToDate", ex); - useCache = false; } #if DEBUG @@ -422,7 +420,7 @@ private void SaveModdedPhysics() configs[0].config.Save(physicsPath); } - private void IsCacheUpToDate() + private bool IsCacheUpToDate() { Stopwatch sw = new Stopwatch(); sw.Start(); @@ -471,7 +469,7 @@ private void IsCacheUpToDate() logger.Info("SHA generated in " + ((float)sw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); logger.Info(" SHA = " + configSha); - useCache = false; + bool useCache = false; if (File.Exists(shaPath)) { ConfigNode shaConfigNode = ConfigNode.Load(shaPath); @@ -492,6 +490,7 @@ private void IsCacheUpToDate() logger.Info("useCache = " + useCache); } } + return useCache; } private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) From 02bd5817ec393dbecc9cc1eb0143aed2b5389723 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 9 Dec 2017 23:15:59 -0800 Subject: [PATCH 209/342] Make Patch an object Saves having to parse things multiple times, error/info messages can now show the original name (except NEEDS, will be worked on). More functionality will be added to this class in future commits. --- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/Patch.cs | 21 ++ ModuleManager/PatchApplier.cs | 45 ++-- ModuleManager/PatchExtractor.cs | 33 +-- ModuleManager/PatchList.cs | 12 +- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PatchApplierTest.cs | 231 ++++++------------- ModuleManagerTests/PatchExtractorTest.cs | 142 ++++++------ ModuleManagerTests/PatchTest.cs | 76 ++++++ 9 files changed, 284 insertions(+), 278 deletions(-) create mode 100644 ModuleManager/Patch.cs create mode 100644 ModuleManagerTests/PatchTest.cs diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 61344d57..c0f51ded 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -59,6 +59,7 @@ + diff --git a/ModuleManager/Patch.cs b/ModuleManager/Patch.cs new file mode 100644 index 00000000..ba443205 --- /dev/null +++ b/ModuleManager/Patch.cs @@ -0,0 +1,21 @@ +using System; + +namespace ModuleManager +{ + public class Patch + { + public readonly UrlDir.UrlConfig urlConfig; + public readonly Command command; + public readonly ConfigNode node; + + public Patch(UrlDir.UrlConfig urlConfig, Command command, ConfigNode node) + { + if (command != Command.Edit && command != Command.Copy && command != Command.Delete) + throw new ArgumentException($"Must be Edit, Copy, or Delete (got {command})", nameof(command)); + + this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); + this.command = command; + this.node = node ?? throw new ArgumentNullException(nameof(node)); + } + } +} diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 5e30ad02..3e981653 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -49,31 +49,30 @@ public void ApplyPatches() ApplyPatches(":FINAL", patchList.finalPatches); } - private void ApplyPatches(string stage, IEnumerable patches) + private void ApplyPatches(string stage, IEnumerable patches) { logger.Info(stage + " pass"); Activity = "ModuleManager " + stage; - foreach (UrlDir.UrlConfig mod in patches) + foreach (Patch patch in patches) { try { - string name = mod.type.RemoveWS(); - Command cmd = CommandParser.Parse(name, out string tmp); + string name = patch.node.name.RemoveWS(); - if (cmd == Command.Insert) + if (patch.command == Command.Insert) { - logger.Warning("Warning - Encountered insert node that should not exist at this stage: " + mod.SafeUrl()); + logger.Warning("Warning - Encountered insert node that should not exist at this stage: " + patch.urlConfig.SafeUrl()); continue; } - else if (cmd != Command.Edit && cmd != Command.Copy && cmd != Command.Delete) + else if (patch.command != Command.Edit && patch.command != Command.Copy && patch.command != Command.Delete) { - logger.Warning("Invalid command encountered on a patch: " + mod.SafeUrl()); + logger.Warning("Invalid command encountered on a patch: " + patch.urlConfig.SafeUrl()); continue; } string upperName = name.ToUpper(); - PatchContext context = new PatchContext(mod, databaseRoot, logger, progress); + PatchContext context = new PatchContext(patch.urlConfig, databaseRoot, logger, progress); char[] sep = { '[', ']' }; string condition = ""; @@ -86,29 +85,29 @@ private void ApplyPatches(string stage, IEnumerable patches) string[] splits = name.Split(sep, 3); string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : null; - string type = splits[0].Substring(1); + string type = splits[0]; - bool loop = mod.config.HasNode("MM_PATCH_LOOP"); + bool loop = patch.node.HasNode("MM_PATCH_LOOP"); foreach (UrlDir.UrlFile file in allConfigFiles) { - if (cmd == Command.Edit) + if (patch.command == Command.Edit) { foreach (UrlDir.UrlConfig url in file.configs) { if (!IsMatch(url, type, patterns, condition)) continue; - if (loop) logger.Info("Looping on " + mod.SafeUrl() + " to " + url.SafeUrl()); + if (loop) logger.Info($"Looping on {patch.urlConfig.SafeUrl()} to {url.SafeUrl()}"); do { - progress.ApplyingUpdate(url, mod); - url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + progress.ApplyingUpdate(url, patch.urlConfig); + url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), patch.node, context); } while (loop && IsMatch(url, type, patterns, condition)); if (loop) url.config.RemoveNodes("MM_PATCH_LOOP"); } } - else if (cmd == Command.Copy) + else if (patch.command == Command.Copy) { // Avoid checking the new configs we are creating int count = file.configs.Count; @@ -117,19 +116,19 @@ private void ApplyPatches(string stage, IEnumerable patches) UrlDir.UrlConfig url = file.configs[i]; if (!IsMatch(url, type, patterns, condition)) continue; - ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), mod.config, context); + ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), patch.node, context); if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) { - progress.Error(mod, $"Error - when applying copy {mod.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); + progress.Error(patch.urlConfig, $"Error - when applying copy {patch.urlConfig.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); } else { - progress.ApplyingCopy(url, mod); + progress.ApplyingCopy(url, patch.urlConfig); file.AddConfig(clone); } } } - else if (cmd == Command.Delete) + else if (patch.command == Command.Delete) { int i = 0; while (i < file.configs.Count) @@ -138,7 +137,7 @@ private void ApplyPatches(string stage, IEnumerable patches) if (IsMatch(url, type, patterns, condition)) { - progress.ApplyingDelete(url, mod); + progress.ApplyingDelete(url, patch.urlConfig); file.configs.RemoveAt(i); } else @@ -156,11 +155,11 @@ private void ApplyPatches(string stage, IEnumerable patches) } catch (Exception e) { - progress.Exception(mod, "Exception while processing node : " + mod.SafeUrl(), e); + progress.Exception(patch.urlConfig, "Exception while processing node : " + patch.urlConfig.SafeUrl(), e); try { - logger.Error("Processed node was\n" + mod.PrettyPrint()); + logger.Error("Processed node was\n" + patch.urlConfig.PrettyPrint()); } catch (Exception ex2) { diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index d3425918..93210f2d 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -31,13 +31,13 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable thePass = null; + List thePass = null; bool modNotFound = false; if (firstMatch.Success) @@ -176,16 +176,17 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable beforePatches = new List(0); - public readonly List forPatches = new List(0); - public readonly List afterPatches = new List(0); + public readonly List beforePatches = new List(0); + public readonly List forPatches = new List(0); + public readonly List afterPatches = new List(0); public readonly string name; @@ -52,9 +52,9 @@ public ModPassCollection(IEnumerable modList) IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - public readonly List firstPatches = new List(); - public readonly List legacyPatches = new List(); - public readonly List finalPatches = new List(); + public readonly List firstPatches = new List (); + public readonly List legacyPatches = new List(); + public readonly List finalPatches = new List(); public readonly ModPassCollection modPasses; diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 1909a654..72faea68 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -71,6 +71,7 @@ + diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index 729de882..1370095a 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -5,6 +5,7 @@ using UnityEngine; using TestUtils; using ModuleManager; +using ModuleManager.Extensions; using ModuleManager.Logging; using ModuleManager.Progress; @@ -50,7 +51,7 @@ public void TestApplyPatches__Edit() { "jkl", "mno" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART") + Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART") { { "@foo", "baz" }, { "pqr", "stw" }, @@ -63,8 +64,8 @@ public void TestApplyPatches__Edit() EnsureNoErrors(); progress.Received(1).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1); - progress.Received().ApplyingUpdate(config2, patch1); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingUpdate(config2, patch1.urlConfig); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(3, allConfigs.Length); @@ -109,7 +110,7 @@ public void TestApplyPatches__Copy() { "bbb", "004" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART") + Patch patch1 = CreatePatch(Command.Copy, new TestConfigNode("+PART") { { "@name ^", ":^00:01:" }, { "@aaa", "011" }, @@ -123,79 +124,8 @@ public void TestApplyPatches__Copy() EnsureNoErrors(); progress.Received(1).PatchApplied(); - progress.Received().ApplyingCopy(config1, patch1); - progress.Received().ApplyingCopy(config2, patch1); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(5, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, allConfigs[0].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "002" }, - }, allConfigs[1].config); - - AssertNodesEqual(new TestConfigNode("PORT") - { - { "name", "003" }, - { "bbb", "004" }, - }, allConfigs[2].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "010" }, - { "aaa", "011" }, - { "ccc", "005" }, - }, allConfigs[3].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "012" }, - { "ccc", "005" }, - }, allConfigs[4].config); - } - - [Fact] - public void TestApplyPatches__Copy__AlternateCommand() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, file); - - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "002" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") - { - { "name", "003" }, - { "bbb", "004" }, - }, file); - - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("$PART") - { - { "@name ^", ":^00:01:" }, - { "@aaa", "011" }, - { "ccc", "005" }, - }); - - patchList.firstPatches.Add(patch1); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - progress.Received(1).PatchApplied(); - progress.Received().ApplyingCopy(config1, patch1); - progress.Received().ApplyingCopy(config2, patch1); + progress.Received().ApplyingCopy(config1, patch1.urlConfig); + progress.Received().ApplyingCopy(config2, patch1.urlConfig); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(5, allConfigs.Length); @@ -251,49 +181,7 @@ public void TestApplyPatches__Delete() { "jkl", "mno" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("-PART")); - - patchList.firstPatches.Add(patch1); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - progress.Received(1).PatchApplied(); - progress.Received().ApplyingDelete(config1, patch1); - progress.Received().ApplyingDelete(config2, patch1); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(1, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PORT") - { - { "name", "ghi" }, - { "jkl", "mno" }, - }, allConfigs[0].config); - } - - [Fact] - public void TestApplyPatches__Delete__AlternateCommand() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "abc" }, - { "foo", "bar" }, - }, file); - - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "def" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") - { - { "name", "ghi" }, - { "jkl", "mno" }, - }, file); - - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("-PART")); + Patch patch1 = CreatePatch(Command.Delete, new TestConfigNode("-PART")); patchList.firstPatches.Add(patch1); @@ -302,8 +190,8 @@ public void TestApplyPatches__Delete__AlternateCommand() EnsureNoErrors(); progress.Received(1).PatchApplied(); - progress.Received().ApplyingDelete(config1, patch1); - progress.Received().ApplyingDelete(config2, patch1); + progress.Received().ApplyingDelete(config1, patch1.urlConfig); + progress.Received().ApplyingDelete(config2, patch1.urlConfig); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); @@ -342,20 +230,20 @@ public void TestApplyPatches__Name() { "ddd", "007" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000]") + Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000]") { { "@aaa", "011" }, { "eee", "012" }, }); - UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART[002]") + Patch patch2 = CreatePatch(Command.Copy, new TestConfigNode("+PART[002]") { { "@name", "022" }, { "@bbb", "013" }, { "fff", "014" }, }); - UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new TestConfigNode("!PART[004]")); + Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART[004]")); patchList.firstPatches.Add(patch1); patchList.firstPatches.Add(patch2); @@ -366,9 +254,9 @@ public void TestApplyPatches__Name() EnsureNoErrors(); progress.Received(3).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1); - progress.Received().ApplyingCopy(config2, patch2); - progress.Received().ApplyingDelete(config3, patch3); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingCopy(config2, patch2.urlConfig); + progress.Received().ApplyingDelete(config3, patch3.urlConfig); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(4, allConfigs.Length); @@ -427,20 +315,20 @@ public void TestApplyPatches__Name__Wildcard() { "ddd", "007" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[0*0]") + Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART[0*0]") { { "@aaa", "011" }, { "eee", "012" }, }); - UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART[0*2]") + Patch patch2 = CreatePatch(Command.Copy, new TestConfigNode("+PART[0*2]") { { "@name", "022" }, { "@bbb", "013" }, { "fff", "014" }, }); - UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new TestConfigNode("!PART[0*4]")); + Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART[0*4]")); patchList.firstPatches.Add(patch1); patchList.firstPatches.Add(patch2); @@ -451,9 +339,9 @@ public void TestApplyPatches__Name__Wildcard() EnsureNoErrors(); progress.Received(3).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1); - progress.Received().ApplyingCopy(config2, patch2); - progress.Received().ApplyingDelete(config3, patch3); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingCopy(config2, patch2.urlConfig); + progress.Received().ApplyingDelete(config3, patch3.urlConfig); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(4, allConfigs.Length); @@ -506,7 +394,7 @@ public void TestApplyPatches__Name__Or() { "ccc", "005" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "@aaa", "011" }, { "ddd", "006" }, @@ -519,8 +407,8 @@ public void TestApplyPatches__Name__Or() EnsureNoErrors(); progress.Received(1).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1); - progress.Received().ApplyingUpdate(config2, patch1); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingUpdate(config2, patch1.urlConfig); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(3, allConfigs.Length); @@ -555,47 +443,47 @@ public void TestApplyPatches__Order() { "aaa", "001" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "bbb", "002" }, }); - UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch2 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "ccc", "003" }, }); - UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch3 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "ddd", "004" }, }); - UrlDir.UrlConfig patch4 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch4 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "eee", "005" }, }); - UrlDir.UrlConfig patch5 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch5 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "fff", "006" }, }); - UrlDir.UrlConfig patch6 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch6 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "ggg", "007" }, }); - UrlDir.UrlConfig patch7 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch7 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "hhh", "008" }, }); - UrlDir.UrlConfig patch8 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch8 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "iii", "009" }, }); - UrlDir.UrlConfig patch9 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART[000|0*2]") + Patch patch9 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") { { "jjj", "010" }, }); @@ -615,15 +503,15 @@ public void TestApplyPatches__Order() EnsureNoErrors(); progress.Received(9).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1); - progress.Received().ApplyingUpdate(config1, patch2); - progress.Received().ApplyingUpdate(config1, patch3); - progress.Received().ApplyingUpdate(config1, patch4); - progress.Received().ApplyingUpdate(config1, patch5); - progress.Received().ApplyingUpdate(config1, patch6); - progress.Received().ApplyingUpdate(config1, patch7); - progress.Received().ApplyingUpdate(config1, patch8); - progress.Received().ApplyingUpdate(config1, patch9); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingUpdate(config1, patch2.urlConfig); + progress.Received().ApplyingUpdate(config1, patch3.urlConfig); + progress.Received().ApplyingUpdate(config1, patch4.urlConfig); + progress.Received().ApplyingUpdate(config1, patch5.urlConfig); + progress.Received().ApplyingUpdate(config1, patch6.urlConfig); + progress.Received().ApplyingUpdate(config1, patch7.urlConfig); + progress.Received().ApplyingUpdate(config1, patch8.urlConfig); + progress.Received().ApplyingUpdate(config1, patch9.urlConfig); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); @@ -671,20 +559,20 @@ public void TestApplyPatches__Constraints() { "ddd", "007" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART:HAS[#aaa[001]]") + Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART:HAS[#aaa[001]]") { { "@aaa", "011" }, { "eee", "012" }, }); - UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART:HAS[#bbb[003]]") + Patch patch2 = CreatePatch(Command.Copy, new TestConfigNode("+PART:HAS[#bbb[003]]") { { "@name", "012" }, { "@bbb", "013" }, { "fff", "014" }, }); - UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new TestConfigNode("!PART:HAS[#ccc[005]]")); + Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART:HAS[#ccc[005]]")); patchList.firstPatches.Add(patch1); patchList.firstPatches.Add(patch2); @@ -695,9 +583,9 @@ public void TestApplyPatches__Constraints() EnsureNoErrors(); progress.Received(3).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1); - progress.Received().ApplyingCopy(config2, patch2); - progress.Received().ApplyingDelete(config3, patch3); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingCopy(config2, patch2.urlConfig); + progress.Received().ApplyingDelete(config3, patch3.urlConfig); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(4, allConfigs.Length); @@ -738,7 +626,7 @@ public void TestApplyPatches__Loop() { "aaa", "1" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("@PART:HAS[~aaa[>10]]") + Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART:HAS[~aaa[>10]]") { { "@aaa *", "2" }, { "bbb", "002" }, @@ -752,7 +640,7 @@ public void TestApplyPatches__Loop() EnsureNoErrors(); progress.Received(1).PatchApplied(); - progress.Received(4).ApplyingUpdate(config1, patch1); + progress.Received(4).ApplyingUpdate(config1, patch1.urlConfig); logger.Received().Log(LogType.Log, "Looping on abc/def/@PART:HAS[~aaa[>10]] to abc/def/PART"); @@ -770,6 +658,7 @@ public void TestApplyPatches__Loop() }, allConfigs[0].config); } + /* [Fact] public void TestApplyPatches__InvalidOperator() { @@ -816,6 +705,7 @@ public void TestApplyPatches__InvalidOperator() }, allConfigs[0].config); } + */ [Fact] public void TestApplyPatches__Copy__NameNotChanged() @@ -826,7 +716,7 @@ public void TestApplyPatches__Copy__NameNotChanged() { "aaa", "001" }, }, file); - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new TestConfigNode("+PART") + Patch patch1 = CreatePatch(Command.Copy, new TestConfigNode("+PART") { { "@aaa", "011" }, { "bbb", "012" }, @@ -839,7 +729,7 @@ public void TestApplyPatches__Copy__NameNotChanged() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.Received().Error(patch1, "Error - when applying copy abc/def/+PART to abc/def/PART - the copy needs to have a different name than the parent (use @name = xxx)"); + progress.Received().Error(patch1.urlConfig, "Error - when applying copy abc/def/+PART to abc/def/PART - the copy needs to have a different name than the parent (use @name = xxx)"); logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); logger.DidNotReceive().Log(LogType.Error, Arg.Any()); @@ -873,5 +763,16 @@ private void AssertNodesEqual(ConfigNode expected, ConfigNode actual) { Assert.Equal(expected.ToString(), actual.ToString()); } + + private Patch CreatePatch(Command command, ConfigNode node) + { + ConfigNode newNode = node; + if (command != Command.Insert) + { + newNode = new ConfigNode(node.name.Substring(1)); + newNode.ShallowCopyFrom(node); + } + return new Patch(new UrlDir.UrlConfig(file, node), command, newNode); + } } } diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 2a56c11a..3f9005b1 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -139,65 +139,68 @@ public void TestSortAndExtractPatches() Assert.Equal(insertConfigs, root.AllConfigs); - Assert.Equal(legacyConfigs, list.legacyPatches); + List currentPatches; - List currentPatches; + currentPatches = list.legacyPatches; + Assert.Equal(legacyConfigs.Length, currentPatches.Count); + AssertPatchCorrect(currentPatches[0], legacyConfigs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], legacyConfigs[1], Command.Edit, "NADE[foo]:HAS[#bar]"); currentPatches = list.firstPatches; Assert.Equal(firstConfigs.Length, currentPatches.Count); - AssertUrlCorrect("@NODE", firstConfigs[0], currentPatches[0]); - AssertUrlCorrect("@NODE[foo]:HAS[#bar]", firstConfigs[1], currentPatches[1]); - AssertUrlCorrect("@NADE", firstConfigs[2], currentPatches[2]); - AssertUrlCorrect("@NADE", firstConfigs[3], currentPatches[3]); + AssertPatchCorrect(currentPatches[0], firstConfigs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], firstConfigs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(currentPatches[2], firstConfigs[2], Command.Edit, "NADE"); + AssertPatchCorrect(currentPatches[3], firstConfigs[3], Command.Edit, "NADE"); currentPatches = list.finalPatches; Assert.Equal(finalConfigs.Length, currentPatches.Count); - AssertUrlCorrect("@NODE", finalConfigs[0], currentPatches[0]); - AssertUrlCorrect("@NODE[foo]:HAS[#bar]", finalConfigs[1], currentPatches[1]); - AssertUrlCorrect("@NADE", finalConfigs[2], currentPatches[2]); - AssertUrlCorrect("@NADE", finalConfigs[3], currentPatches[3]); + AssertPatchCorrect(currentPatches[0], finalConfigs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], finalConfigs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(currentPatches[2], finalConfigs[2], Command.Edit, "NADE"); + AssertPatchCorrect(currentPatches[3], finalConfigs[3], Command.Edit, "NADE"); currentPatches = list.modPasses["mod1"].beforePatches; Assert.Equal(beforeMod1Configs.Length, currentPatches.Count); - AssertUrlCorrect("@NODE", beforeMod1Configs[0], currentPatches[0]); - AssertUrlCorrect("@NODE[foo]:HAS[#bar]", beforeMod1Configs[1], currentPatches[1]); - AssertUrlCorrect("@NADE", beforeMod1Configs[2], currentPatches[2]); - AssertUrlCorrect("@NADE", beforeMod1Configs[3], currentPatches[3]); + AssertPatchCorrect(currentPatches[0], beforeMod1Configs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], beforeMod1Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(currentPatches[2], beforeMod1Configs[2], Command.Edit, "NADE"); + AssertPatchCorrect(currentPatches[3], beforeMod1Configs[3], Command.Edit, "NADE"); currentPatches = list.modPasses["mod1"].forPatches; Assert.Equal(forMod1Configs.Length, currentPatches.Count); - AssertUrlCorrect("@NODE", forMod1Configs[0], currentPatches[0]); - AssertUrlCorrect("@NODE[foo]:HAS[#bar]", forMod1Configs[1], currentPatches[1]); - AssertUrlCorrect("@NADE", forMod1Configs[2], currentPatches[2]); - AssertUrlCorrect("@NADE", forMod1Configs[3], currentPatches[3]); + AssertPatchCorrect(currentPatches[0], forMod1Configs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], forMod1Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(currentPatches[2], forMod1Configs[2], Command.Edit, "NADE"); + AssertPatchCorrect(currentPatches[3], forMod1Configs[3], Command.Edit, "NADE"); currentPatches = list.modPasses["mod1"].afterPatches; Assert.Equal(afterMod1Configs.Length, currentPatches.Count); - AssertUrlCorrect("@NODE", afterMod1Configs[0], currentPatches[0]); - AssertUrlCorrect("@NODE[foo]:HAS[#bar]", afterMod1Configs[1], currentPatches[1]); - AssertUrlCorrect("@NADE", afterMod1Configs[2], currentPatches[2]); - AssertUrlCorrect("@NADE", afterMod1Configs[3], currentPatches[3]); + AssertPatchCorrect(currentPatches[0], afterMod1Configs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], afterMod1Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(currentPatches[2], afterMod1Configs[2], Command.Edit, "NADE"); + AssertPatchCorrect(currentPatches[3], afterMod1Configs[3], Command.Edit, "NADE"); currentPatches = list.modPasses["mod2"].beforePatches; Assert.Equal(beforeMod2Configs.Length, currentPatches.Count); - AssertUrlCorrect("@NODE", beforeMod2Configs[0], currentPatches[0]); - AssertUrlCorrect("@NODE[foo]:HAS[#bar]", beforeMod2Configs[1], currentPatches[1]); - AssertUrlCorrect("@NADE", beforeMod2Configs[2], currentPatches[2]); - AssertUrlCorrect("@NADE", beforeMod2Configs[3], currentPatches[3]); + AssertPatchCorrect(currentPatches[0], beforeMod2Configs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], beforeMod2Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(currentPatches[2], beforeMod2Configs[2], Command.Edit, "NADE"); + AssertPatchCorrect(currentPatches[3], beforeMod2Configs[3], Command.Edit, "NADE"); currentPatches = list.modPasses["mod2"].forPatches; Assert.Equal(forMod2Configs.Length, currentPatches.Count); - AssertUrlCorrect("@NODE", forMod2Configs[0], currentPatches[0]); - AssertUrlCorrect("@NODE[foo]:HAS[#bar]", forMod2Configs[1], currentPatches[1]); - AssertUrlCorrect("@NADE", forMod2Configs[2], currentPatches[2]); - AssertUrlCorrect("@NADE", forMod2Configs[3], currentPatches[3]); + AssertPatchCorrect(currentPatches[0], forMod2Configs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], forMod2Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(currentPatches[2], forMod2Configs[2], Command.Edit, "NADE"); + AssertPatchCorrect(currentPatches[3], forMod2Configs[3], Command.Edit, "NADE"); currentPatches = list.modPasses["mod2"].afterPatches; Assert.Equal(afterMod2Configs.Length, currentPatches.Count); - AssertUrlCorrect("@NODE", afterMod2Configs[0], currentPatches[0]); - AssertUrlCorrect("@NODE[foo]:HAS[#bar]", afterMod2Configs[1], currentPatches[1]); - AssertUrlCorrect("@NADE", afterMod2Configs[2], currentPatches[2]); - AssertUrlCorrect("@NADE", afterMod2Configs[3], currentPatches[3]); + AssertPatchCorrect(currentPatches[0], afterMod2Configs[0], Command.Edit, "NODE"); + AssertPatchCorrect(currentPatches[1], afterMod2Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(currentPatches[2], afterMod2Configs[2], Command.Edit, "NADE"); + AssertPatchCorrect(currentPatches[3], afterMod2Configs[3], Command.Edit, "NADE"); progress.Received(34).PatchAdded(); @@ -260,7 +263,7 @@ public void TestSortAndExtractPatches__MoreThanOnePass() progress.Received().Error(config3, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FOR[mod1]"); Assert.Equal(1, list.firstPatches.Count); - AssertUrlCorrect("@NODE", config1, list.firstPatches[0]); + AssertPatchCorrect(list.firstPatches[0], config1, Command.Edit, "NODE"); Assert.Empty(list.legacyPatches); Assert.Empty(list.finalPatches); Assert.Empty(list.modPasses["mod1"].beforePatches); @@ -285,9 +288,10 @@ public void TestSortAndExtractPatches__Exception() progress.Received().Exception(config2, "Exception while parsing pass for config: abc/def/@NODE:FIRST:FIRST", e); - Assert.Equal(new[] { config1 }, list.legacyPatches); + Assert.Equal(1, list.legacyPatches.Count); + AssertPatchCorrect(list.legacyPatches[0], config1, Command.Edit, "NODE"); Assert.Equal(1, list.firstPatches.Count); - AssertUrlCorrect("@NADE", config3, list.firstPatches[0]); + AssertPatchCorrect(list.firstPatches[0], config3, Command.Edit, "NADE"); progress.Received(2).PatchAdded(); } @@ -312,7 +316,7 @@ public void TestSortAndExtractPatches__NotBracketBalanced() Assert.Empty(list.finalPatches); Assert.Empty(list.modPasses["mod1"].beforePatches); Assert.Equal(1, list.modPasses["mod1"].forPatches.Count); - AssertUrlCorrect("@NODE", config1, list.modPasses["mod1"].forPatches[0]); + AssertPatchCorrect(list.modPasses["mod1"].forPatches[0], config1, Command.Edit, "NODE"); Assert.Empty(list.modPasses["mod1"].afterPatches); progress.Received(1).PatchAdded(); @@ -341,42 +345,50 @@ public void TestSortAndExtractPatches__BadlyFormed() Assert.Empty(list.finalPatches); Assert.Empty(list.modPasses["mod1"].beforePatches); Assert.Equal(1, list.modPasses["mod1"].forPatches.Count); - AssertUrlCorrect("@NODE", config1, list.modPasses["mod1"].forPatches[0]); + AssertPatchCorrect(list.modPasses["mod1"].forPatches[0], config1, Command.Edit, "NODE"); Assert.Empty(list.modPasses["mod1"].afterPatches); progress.Received(1).PatchAdded(); } [Fact] - public void TestSortAndExtractPatches__InvalidCommand() + public void TestSortAndExtractPatches__Command() { - UrlDir.UrlConfig config1 = CreateConfig("@NODE:FOR[mod1]"); - UrlDir.UrlConfig config2 = CreateConfig("%NODE:FOR[mod1]"); - UrlDir.UrlConfig config3 = CreateConfig("&NODE:FOR[mod1]"); - UrlDir.UrlConfig config4 = CreateConfig("|NODE:FOR[mod1]"); - UrlDir.UrlConfig config5 = CreateConfig("#NODE:FOR[mod1]"); - UrlDir.UrlConfig config6 = CreateConfig("*NODE:FOR[mod1]"); + UrlDir.UrlConfig config01 = CreateConfig("@NODE:FOR[mod1]"); + UrlDir.UrlConfig config02 = CreateConfig("+NODE:FOR[mod1]"); + UrlDir.UrlConfig config03 = CreateConfig("$NODE:FOR[mod1]"); + UrlDir.UrlConfig config04 = CreateConfig("!NODE:FOR[mod1]"); + UrlDir.UrlConfig config05 = CreateConfig("-NODE:FOR[mod1]"); + UrlDir.UrlConfig config06 = CreateConfig("%NODE:FOR[mod1]"); + UrlDir.UrlConfig config07 = CreateConfig("&NODE:FOR[mod1]"); + UrlDir.UrlConfig config08 = CreateConfig("|NODE:FOR[mod1]"); + UrlDir.UrlConfig config09 = CreateConfig("#NODE:FOR[mod1]"); + UrlDir.UrlConfig config10 = CreateConfig("*NODE:FOR[mod1]"); string[] modList = { "mod1" }; PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); Assert.Empty(root.AllConfigs); - progress.Received().Error(config2, "Error - replace command (%) is not valid on a root node: abc/def/%NODE:FOR[mod1]"); - progress.Received().Error(config3, "Error - create command (&) is not valid on a root node: abc/def/&NODE:FOR[mod1]"); - progress.Received().Error(config4, "Error - rename command (|) is not valid on a root node: abc/def/|NODE:FOR[mod1]"); - progress.Received().Error(config5, "Error - paste command (#) is not valid on a root node: abc/def/#NODE:FOR[mod1]"); - progress.Received().Error(config6, "Error - special command (*) is not valid on a root node: abc/def/*NODE:FOR[mod1]"); + progress.Received().Error(config06, "Error - replace command (%) is not valid on a root node: abc/def/%NODE:FOR[mod1]"); + progress.Received().Error(config07, "Error - create command (&) is not valid on a root node: abc/def/&NODE:FOR[mod1]"); + progress.Received().Error(config08, "Error - rename command (|) is not valid on a root node: abc/def/|NODE:FOR[mod1]"); + progress.Received().Error(config09, "Error - paste command (#) is not valid on a root node: abc/def/#NODE:FOR[mod1]"); + progress.Received().Error(config10, "Error - special command (*) is not valid on a root node: abc/def/*NODE:FOR[mod1]"); Assert.Empty(list.firstPatches); Assert.Empty(list.legacyPatches); Assert.Empty(list.finalPatches); Assert.Empty(list.modPasses["mod1"].beforePatches); - Assert.Equal(1, list.modPasses["mod1"].forPatches.Count); - AssertUrlCorrect("@NODE", config1, list.modPasses["mod1"].forPatches[0]); + Assert.Equal(5, list.modPasses["mod1"].forPatches.Count); + AssertPatchCorrect(list.modPasses["mod1"].forPatches[0], config01, Command.Edit, "NODE"); + AssertPatchCorrect(list.modPasses["mod1"].forPatches[1], config02, Command.Copy, "NODE"); + AssertPatchCorrect(list.modPasses["mod1"].forPatches[2], config03, Command.Copy, "NODE"); + AssertPatchCorrect(list.modPasses["mod1"].forPatches[3], config04, Command.Delete, "NODE"); + AssertPatchCorrect(list.modPasses["mod1"].forPatches[4], config05, Command.Delete, "NODE"); Assert.Empty(list.modPasses["mod1"].afterPatches); - progress.Received(1).PatchAdded(); + progress.Received(5).PatchAdded(); } private UrlDir.UrlConfig CreateConfig(string name) @@ -395,31 +407,25 @@ private UrlDir.UrlConfig CreateConfig(string name) return UrlBuilder.CreateConfig(node, file); } - private void AssertUrlCorrect(string expectedNodeName, UrlDir.UrlConfig originalUrl, UrlDir.UrlConfig observedUrl) + private void AssertPatchCorrect(Patch patch, UrlDir.UrlConfig originalUrl, Command expectedCommand, string expectedNodeName) { - Assert.Equal(expectedNodeName, observedUrl.type); + Assert.Same(originalUrl, patch.urlConfig); + Assert.Equal(expectedNodeName, patch.node.name); ConfigNode originalNode = originalUrl.config; - ConfigNode observedNode = observedUrl.config; - - Assert.Equal(expectedNodeName, observedNode.name); - - if (originalNode.HasValue("name")) Assert.Equal(originalNode.GetValue("name"), observedUrl.name); - - Assert.Same(originalUrl.parent, observedUrl.parent); - Assert.Equal(originalNode.id, observedNode.id); - Assert.Equal(originalNode.values.Count, observedNode.values.Count); - Assert.Equal(originalNode.nodes.Count, observedNode.nodes.Count); + Assert.Equal(originalNode.id, patch.node.id); + Assert.Equal(originalNode.values.Count, patch.node.values.Count); + Assert.Equal(originalNode.nodes.Count, patch.node.nodes.Count); for (int i = 0; i < originalNode.values.Count; i++) { - Assert.Same(originalNode.values[i], observedNode.values[i]); + Assert.Same(originalNode.values[i], patch.node.values[i]); } for (int i = 0; i < originalNode.nodes.Count; i++) { - Assert.Same(originalNode.nodes[i], observedNode.nodes[i]); + Assert.Same(originalNode.nodes[i], patch.node.nodes[i]); } } } diff --git a/ModuleManagerTests/PatchTest.cs b/ModuleManagerTests/PatchTest.cs new file mode 100644 index 00000000..c8bda099 --- /dev/null +++ b/ModuleManagerTests/PatchTest.cs @@ -0,0 +1,76 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class PatchTest + { + [Fact] + public void TestConstructor() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + ConfigNode node = new ConfigNode("NADE"); + Patch patch = new Patch(urlConfig, Command.Edit, node); + + Assert.Same(urlConfig, patch.urlConfig); + Assert.Equal(Command.Edit, patch.command); + Assert.Same(node, patch.node); + } + + [Fact] + public void TestConstructor__ValidCommands() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + ConfigNode node = new ConfigNode("NADE"); + + Patch patch1 = new Patch(urlConfig, Command.Edit, node); + Assert.Same(urlConfig, patch1.urlConfig); + Assert.Equal(Command.Edit, patch1.command); + Assert.Same(node, patch1.node); + + Patch patch2 = new Patch(urlConfig, Command.Copy, node); + Assert.Same(urlConfig, patch2.urlConfig); + Assert.Equal(Command.Copy, patch2.command); + Assert.Same(node, patch2.node); + + Patch patch3 = new Patch(urlConfig, Command.Delete, node); + Assert.Same(urlConfig, patch3.urlConfig); + Assert.Equal(Command.Delete, patch3.command); + Assert.Same(node, patch3.node); + + ArgumentException ex1 = Assert.Throws(() => new Patch(urlConfig, Command.Create, node)); + Assert.Equal("Must be Edit, Copy, or Delete (got Create)\r\nParameter name: command", ex1.Message); + + ArgumentException ex2 = Assert.Throws(() => new Patch(urlConfig, Command.Insert, node)); + Assert.Equal("Must be Edit, Copy, or Delete (got Insert)\r\nParameter name: command", ex2.Message); + + ArgumentException ex3 = Assert.Throws(() => new Patch(urlConfig, Command.Paste, node)); + Assert.Equal("Must be Edit, Copy, or Delete (got Paste)\r\nParameter name: command", ex3.Message); + + ArgumentException ex4 = Assert.Throws(() => new Patch(urlConfig, Command.Rename, node)); + Assert.Equal("Must be Edit, Copy, or Delete (got Rename)\r\nParameter name: command", ex4.Message); + + ArgumentException ex5 = Assert.Throws(() => new Patch(urlConfig, Command.Replace, node)); + Assert.Equal("Must be Edit, Copy, or Delete (got Replace)\r\nParameter name: command", ex5.Message); + + ArgumentException ex6 = Assert.Throws(() => new Patch(urlConfig, Command.Special, node)); + Assert.Equal("Must be Edit, Copy, or Delete (got Special)\r\nParameter name: command", ex6.Message); + } + + [Fact] + public void TestConstructor__NullUrlConfig() + { + Assert.Throws(() => new Patch(null, Command.Edit, new ConfigNode("BLAH"))); + } + + [Fact] + public void TestConstructor__NullNode() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + Assert.Throws(() => new Patch(urlConfig, Command.Edit, null)); + } + } +} From 9d37955474438a38696ca0b15ae8dc8c451982d1 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 10 Dec 2017 20:57:59 -0800 Subject: [PATCH 210/342] Simplify a bit --- ModuleManager/PatchExtractor.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 93210f2d..6eb02440 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -118,7 +118,6 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable thePass = null; - bool modNotFound = false; if (firstMatch.Success) { @@ -139,8 +138,8 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable Date: Sun, 10 Dec 2017 21:24:46 -0800 Subject: [PATCH 211/342] Extra semicolon --- ModuleManager/PatchExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 6eb02440..e327daea 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -31,7 +31,7 @@ public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable Date: Wed, 7 Feb 2018 21:29:32 -0800 Subject: [PATCH 212/342] Extract interface for PatchList, modify PatchExtractor PatchExtractor did too much at once, it shouldn't enumerate and extract at the same time (still some work to be done there...). PatchList now has a clearer interface and obscures more of the details - iterating through patches is now handled by it rater than PatchApplier --- ModuleManager/Collections/ArrayEnumerator.cs | 2 +- ModuleManager/MMPatchLoader.cs | 11 +- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/Pass.cs | 31 + ModuleManager/PatchApplier.cs | 28 +- ModuleManager/PatchExtractor.cs | 305 ++++--- ModuleManager/PatchList.cs | 119 ++- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PassTest.cs | 73 ++ ModuleManagerTests/PatchApplierTest.cs | 264 +++--- ModuleManagerTests/PatchExtractorTest.cs | 884 +++++++++++++------ ModuleManagerTests/PatchListTest.cs | 312 ++++++- 12 files changed, 1411 insertions(+), 620 deletions(-) create mode 100644 ModuleManager/Pass.cs create mode 100644 ModuleManagerTests/PassTest.cs diff --git a/ModuleManager/Collections/ArrayEnumerator.cs b/ModuleManager/Collections/ArrayEnumerator.cs index d002b2ad..c965b9d3 100644 --- a/ModuleManager/Collections/ArrayEnumerator.cs +++ b/ModuleManager/Collections/ArrayEnumerator.cs @@ -9,7 +9,7 @@ public struct ArrayEnumerator : IEnumerator private readonly T[] array; private int index; - public ArrayEnumerator(T[] array) + public ArrayEnumerator(params T[] array) { this.array = array; index = -1; diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index c786220f..f713c0c3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -175,7 +175,16 @@ private IEnumerator ProcessPatch() yield return null; - PatchList patchList = PatchExtractor.SortAndExtractPatches(GameDatabase.Instance.root, mods, progress); + // PatchList patchList = PatchExtractor.SortAndExtractPatches(GameDatabase.Instance.root, mods, progress); + + PatchList patchList = new PatchList(mods); + PatchExtractor extractor = new PatchExtractor(patchList, progress, logger); + + // Have to convert to an array because we will be removing patches + foreach (UrlDir.UrlConfig urlConfig in GameDatabase.Instance.root.AllConfigs.ToArray()) + { + extractor.ExtractPatch(urlConfig); + } #endregion diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index c0f51ded..4e0bd9da 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -59,6 +59,7 @@ + diff --git a/ModuleManager/Pass.cs b/ModuleManager/Pass.cs new file mode 100644 index 00000000..241c88b5 --- /dev/null +++ b/ModuleManager/Pass.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace ModuleManager +{ + public interface IPass : IEnumerable + { + string Name { get; } + } + + public class Pass : IPass + { + private readonly string name; + private readonly List patches = new List(0); + + public Pass(string name) + { + this.name = name ?? throw new ArgumentNullException(nameof(name)); + if (name == string.Empty) throw new ArgumentException("can't be empty", nameof(name)); + } + + public string Name => name; + + public void Add(Patch patch) => patches.Add(patch); + + public List.Enumerator GetEnumerator() => patches.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 3e981653..c82ee866 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using ModuleManager.Logging; using ModuleManager.Extensions; @@ -14,13 +13,13 @@ public class PatchApplier private readonly IPatchProgress progress; private readonly UrlDir databaseRoot; - private readonly PatchList patchList; + private readonly IPatchList patchList; private readonly UrlDir.UrlFile[] allConfigFiles; public string Activity { get; private set; } - public PatchApplier(PatchList patchList, UrlDir databaseRoot, IPatchProgress progress, IBasicLogger logger) + public PatchApplier(IPatchList patchList, UrlDir databaseRoot, IPatchProgress progress, IBasicLogger logger) { this.patchList = patchList; this.databaseRoot = databaseRoot; @@ -32,29 +31,18 @@ public PatchApplier(PatchList patchList, UrlDir databaseRoot, IPatchProgress pro public void ApplyPatches() { - ApplyPatches(":FIRST", patchList.firstPatches); - - // any node without a :pass - ApplyPatches(":LEGACY (default)", patchList.legacyPatches); - - foreach (PatchList.ModPass pass in patchList.modPasses) + foreach (IPass pass in patchList) { - string upperModName = pass.name.ToUpper(); - ApplyPatches($":BEFORE[{upperModName}]", pass.beforePatches); - ApplyPatches($":FOR[{upperModName}]", pass.forPatches); - ApplyPatches($":AFTER[{upperModName}]", pass.afterPatches); + ApplyPatches(pass); } - - // :Final node - ApplyPatches(":FINAL", patchList.finalPatches); } - private void ApplyPatches(string stage, IEnumerable patches) + private void ApplyPatches(IPass pass) { - logger.Info(stage + " pass"); - Activity = "ModuleManager " + stage; + logger.Info(pass.Name + " pass"); + Activity = "ModuleManager " + pass.Name; - foreach (Patch patch in patches) + foreach (Patch patch in pass) { try { diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index e327daea..5a583ad4 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -1,13 +1,12 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using ModuleManager.Extensions; +using ModuleManager.Logging; using ModuleManager.Progress; namespace ModuleManager { - public static class PatchExtractor + public class PatchExtractor { private static readonly Regex firstRegex = new Regex(@":FIRST", RegexOptions.IgnoreCase); private static readonly Regex finalRegex = new Regex(@":FINAL", RegexOptions.IgnoreCase); @@ -15,190 +14,202 @@ public static class PatchExtractor private static readonly Regex forRegex = new Regex(@":FOR(?:\[([^\[\]]+)\])?", RegexOptions.IgnoreCase); private static readonly Regex afterRegex = new Regex(@":AFTER(?:\[([^\[\]]+)\])?", RegexOptions.IgnoreCase); - public static PatchList SortAndExtractPatches(UrlDir databaseRoot, IEnumerable modList, IPatchProgress progress) + private readonly IPatchList patchList; + private readonly IPatchProgress progress; + private readonly IBasicLogger logger; + + public PatchExtractor(IPatchList patchList, IPatchProgress progress, IBasicLogger logger) { - PatchList list = new PatchList(modList); + this.patchList = patchList ?? throw new ArgumentNullException(nameof(patchList)); + this.progress = progress ?? throw new ArgumentNullException(nameof(progress)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } - // Have to convert to an array because we will be removing patches - foreach (UrlDir.UrlConfig url in databaseRoot.AllConfigs.ToArray()) + public void ExtractPatch(UrlDir.UrlConfig urlConfig) + { + try { - try + if (!urlConfig.type.IsBracketBalanced()) { - if (!url.type.IsBracketBalanced()) - { - progress.Error(url, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\n" + url.SafeUrl()); - url.parent.configs.Remove(url); - continue; - } + progress.Error(urlConfig, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\n" + urlConfig.SafeUrl()); + urlConfig.parent.configs.Remove(urlConfig); + return; + } - Command command = CommandParser.Parse(url.type, out string name); + Command command = CommandParser.Parse(urlConfig.type, out string name); - Match firstMatch = firstRegex.Match(name); - Match finalMatch = finalRegex.Match(name); - Match beforeMatch = beforeRegex.Match(name); - Match forMatch = forRegex.Match(name); - Match afterMatch = afterRegex.Match(name); + Match firstMatch = firstRegex.Match(name); + Match finalMatch = finalRegex.Match(name); + Match beforeMatch = beforeRegex.Match(name); + Match forMatch = forRegex.Match(name); + Match afterMatch = afterRegex.Match(name); - int matchCount = 0; + int matchCount = 0; - if (firstMatch.Success) matchCount++; - if (finalMatch.Success) matchCount++; - if (beforeMatch.Success) matchCount++; - if (forMatch.Success) matchCount++; - if (afterMatch.Success) matchCount++; + if (firstMatch.Success) matchCount++; + if (finalMatch.Success) matchCount++; + if (beforeMatch.Success) matchCount++; + if (forMatch.Success) matchCount++; + if (afterMatch.Success) matchCount++; - if (firstMatch.NextMatch().Success) matchCount++; - if (finalMatch.NextMatch().Success) matchCount++; - if (beforeMatch.NextMatch().Success) matchCount++; - if (forMatch.NextMatch().Success) matchCount++; - if (afterMatch.NextMatch().Success) matchCount++; + if (firstMatch.NextMatch().Success) matchCount++; + if (finalMatch.NextMatch().Success) matchCount++; + if (beforeMatch.NextMatch().Success) matchCount++; + if (forMatch.NextMatch().Success) matchCount++; + if (afterMatch.NextMatch().Success) matchCount++; - bool error = false; + bool error = false; - if (command == Command.Insert && matchCount > 0) - { - progress.Error(url, $"Error - pass specifier detected on an insert node (not a patch): {url.SafeUrl()}"); - error = true; - } - else if (command == Command.Replace) - { - progress.Error(url, $"Error - replace command (%) is not valid on a root node: {url.SafeUrl()}"); - error = true; - } - else if (command == Command.Create) - { - progress.Error(url, $"Error - create command (&) is not valid on a root node: {url.SafeUrl()}"); - error = true; - } - else if (command == Command.Rename) - { - progress.Error(url, $"Error - rename command (|) is not valid on a root node: {url.SafeUrl()}"); - error = true; - } - else if (command == Command.Paste) - { - progress.Error(url, $"Error - paste command (#) is not valid on a root node: {url.SafeUrl()}"); - error = true; - } - else if (command == Command.Special) - { - progress.Error(url, $"Error - special command (*) is not valid on a root node: {url.SafeUrl()}"); - error = true; - } + if (command == Command.Insert && matchCount > 0) + { + progress.Error(urlConfig, $"Error - pass specifier detected on an insert node (not a patch): {urlConfig.SafeUrl()}"); + error = true; + } + else if (command == Command.Replace) + { + progress.Error(urlConfig, $"Error - replace command (%) is not valid on a root node: {urlConfig.SafeUrl()}"); + error = true; + } + else if (command == Command.Create) + { + progress.Error(urlConfig, $"Error - create command (&) is not valid on a root node: {urlConfig.SafeUrl()}"); + error = true; + } + else if (command == Command.Rename) + { + progress.Error(urlConfig, $"Error - rename command (|) is not valid on a root node: {urlConfig.SafeUrl()}"); + error = true; + } + else if (command == Command.Paste) + { + progress.Error(urlConfig, $"Error - paste command (#) is not valid on a root node: {urlConfig.SafeUrl()}"); + error = true; + } + else if (command == Command.Special) + { + progress.Error(urlConfig, $"Error - special command (*) is not valid on a root node: {urlConfig.SafeUrl()}"); + error = true; + } - if (matchCount > 1) - { - progress.Error(url, $"Error - more than one pass specifier on a node: {url.SafeUrl()}"); - error = true; - } - if (beforeMatch.Success && !beforeMatch.Groups[1].Success) - { - progress.Error(url, "Error - malformed :BEFORE patch specifier detected: " + url.SafeUrl()); - error = true; - } - if (forMatch.Success && !forMatch.Groups[1].Success) - { - progress.Error(url, "Error - malformed :FOR patch specifier detected: " + url.SafeUrl()); - error = true; - } - if (afterMatch.Success && !afterMatch.Groups[1].Success) - { - progress.Error(url, "Error - malformed :AFTER patch specifier detected: " + url.SafeUrl()); - error = true; - } - if (error) - { - url.parent.configs.Remove(url); - continue; - } + if (matchCount > 1) + { + progress.Error(urlConfig, $"Error - more than one pass specifier on a node: {urlConfig.SafeUrl()}"); + error = true; + } + if (beforeMatch.Success && !beforeMatch.Groups[1].Success) + { + progress.Error(urlConfig, "Error - malformed :BEFORE patch specifier detected: " + urlConfig.SafeUrl()); + error = true; + } + if (forMatch.Success && !forMatch.Groups[1].Success) + { + progress.Error(urlConfig, "Error - malformed :FOR patch specifier detected: " + urlConfig.SafeUrl()); + error = true; + } + if (afterMatch.Success && !afterMatch.Groups[1].Success) + { + progress.Error(urlConfig, "Error - malformed :AFTER patch specifier detected: " + urlConfig.SafeUrl()); + error = true; + } + if (error) + { + urlConfig.parent.configs.Remove(urlConfig); + return; + } - if (command == Command.Insert) continue; + if (command == Command.Insert) return; - url.parent.configs.Remove(url); + urlConfig.parent.configs.Remove(urlConfig); - Match theMatch = null; - List thePass = null; + Match theMatch = null; + Action addPatch = null; - if (firstMatch.Success) + if (firstMatch.Success) + { + theMatch = firstMatch; + addPatch = patchList.AddFirstPatch; + } + else if (finalMatch.Success) + { + theMatch = finalMatch; + addPatch = patchList.AddFinalPatch; + } + else if (beforeMatch.Success) + { + if (CheckMod(beforeMatch, patchList, out string theMod)) { - theMatch = firstMatch; - thePass = list.firstPatches; + theMatch = beforeMatch; + addPatch = p => patchList.AddBeforePatch(theMod, p); } - else if (finalMatch.Success) + else { - theMatch = finalMatch; - thePass = list.finalPatches; + progress.NeedsUnsatisfiedBefore(urlConfig); + return; } - else if (beforeMatch.Success) + } + else if (forMatch.Success) + { + if (CheckMod(forMatch, patchList, out string theMod)) { - if (CheckMod(beforeMatch, list.modPasses, out string theMod)) - { - theMatch = beforeMatch; - thePass = list.modPasses[theMod].beforePatches; - } - else - { - progress.NeedsUnsatisfiedBefore(url); - continue; - } + theMatch = forMatch; + addPatch = p => patchList.AddForPatch(theMod, p); } - else if (forMatch.Success) + else { - if (CheckMod(forMatch, list.modPasses, out string theMod)) - { - theMatch = forMatch; - thePass = list.modPasses[theMod].forPatches; - } - else - { - progress.NeedsUnsatisfiedFor(url); - continue; - } + progress.NeedsUnsatisfiedFor(urlConfig); + return; } - else if (afterMatch.Success) + } + else if (afterMatch.Success) + { + if (CheckMod(afterMatch, patchList, out string theMod)) { - if (CheckMod(afterMatch, list.modPasses, out string theMod)) - { - theMatch = afterMatch; - thePass = list.modPasses[theMod].afterPatches; - } - else - { - progress.NeedsUnsatisfiedAfter(url); - continue; - } + theMatch = afterMatch; + addPatch = p => patchList.AddAfterPatch(theMod, p); } else { - thePass = list.legacyPatches; + progress.NeedsUnsatisfiedAfter(urlConfig); + return; } + } + else + { + addPatch = patchList.AddLegacyPatch; + } - string newName; - if (theMatch == null) - newName = name; - else - newName = name.Remove(theMatch.Index, theMatch.Length); + string newName; + if (theMatch == null) + newName = name; + else + newName = name.Remove(theMatch.Index, theMatch.Length); - ConfigNode node = new ConfigNode(newName) { id = url.config.id }; - node.ShallowCopyFrom(url.config); - Patch patch = new Patch(url, command, node); + ConfigNode node = new ConfigNode(newName) { id = urlConfig.config.id }; + node.ShallowCopyFrom(urlConfig.config); + Patch patch = new Patch(urlConfig, command, node); - thePass.Add(patch); - progress.PatchAdded(); + addPatch(patch); + progress.PatchAdded(); + } + catch(Exception e) + { + progress.Exception(urlConfig, $"Exception while parsing pass for config: {urlConfig.SafeUrl()}", e); + + try + { + urlConfig.parent.configs.Remove(urlConfig); } - catch(Exception e) + catch (Exception ex) { - progress.Exception(url, $"Exception while parsing pass for config: {url.SafeUrl()}", e); + logger.Exception("Exception while attempting to clean up bad config", ex); } } - - return list; } - private static bool CheckMod(Match match, PatchList.ModPassCollection modPasses, out string theMod) + private static bool CheckMod(Match match, IPatchList patchList, out string theMod) { theMod = match.Groups[1].Value.Trim().ToLower(); - return modPasses.HasMod(theMod); + return patchList.HasMod(theMod); } } } diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index 974d1d1e..dff2fdb5 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -6,23 +6,43 @@ namespace ModuleManager { - public class PatchList + public interface IPatchList : IEnumerable { - public class ModPass - { - public readonly List beforePatches = new List(0); - public readonly List forPatches = new List(0); - public readonly List afterPatches = new List(0); + bool HasMod(string mod); + void AddFirstPatch(Patch patch); + void AddLegacyPatch(Patch patch); + void AddBeforePatch(string mod, Patch patch); + void AddForPatch(string mod, Patch patch); + void AddAfterPatch(string mod, Patch patch); + void AddFinalPatch(Patch patch); + } + public class PatchList : IPatchList + { + private class ModPass + { public readonly string name; - + public readonly Pass beforePass; + public readonly Pass forPass; + public readonly Pass afterPass; + public ModPass(string name) { - this.name = name; + if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == string.Empty) throw new ArgumentException("can't be blank", nameof(name)); + this.name = name.ToUpperInvariant(); + + beforePass = new Pass($":BEFORE[{this.name}]"); + forPass = new Pass($":FOR[{this.name}]"); + afterPass = new Pass($":AFTER[{this.name}]"); } + + public void AddBeforePatch(Patch patch) => beforePass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + public void AddForPatch(Patch patch) => forPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + public void AddAfterPatch(Patch patch) => afterPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); } - public class ModPassCollection : IEnumerable + private class ModPassCollection : IEnumerable { private readonly ModPass[] passesArray; private readonly Dictionary passesDict; @@ -44,23 +64,96 @@ public ModPassCollection(IEnumerable modList) } public ModPass this[string name] => passesDict[name.ToLowerInvariant()]; + public ModPass this[int index] => passesArray[index]; public bool HasMod(string name) => passesDict.ContainsKey(name.ToLowerInvariant()); + public int Count => passesArray.Length; + public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(passesArray); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - public readonly List firstPatches = new List (); - public readonly List legacyPatches = new List(); - public readonly List finalPatches = new List(); + private readonly Pass firstPatches = new Pass(":FIRST"); + private readonly Pass legacyPatches = new Pass(":LEGACY (default)"); + private readonly Pass finalPatches = new Pass(":FINAL"); - public readonly ModPassCollection modPasses; + private readonly ModPassCollection modPasses; public PatchList(IEnumerable modList) { modPasses = new ModPassCollection(modList); } + + public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(EnumeratePasses()); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public bool HasMod(string mod) + { + if (mod == null) throw new ArgumentNullException(nameof(mod)); + if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); + return modPasses.HasMod(mod); + } + + public void AddFirstPatch(Patch patch) + { + firstPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + } + + public void AddLegacyPatch(Patch patch) + { + legacyPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + } + + public void AddBeforePatch(string mod, Patch patch) + { + EnsureMod(mod); + modPasses[mod].AddBeforePatch(patch ?? throw new ArgumentNullException(nameof(patch))); + } + + public void AddForPatch(string mod, Patch patch) + { + EnsureMod(mod); + modPasses[mod].AddForPatch(patch ?? throw new ArgumentNullException(nameof(patch))); + } + + public void AddAfterPatch(string mod, Patch patch) + { + EnsureMod(mod); + modPasses[mod].AddAfterPatch(patch ?? throw new ArgumentNullException(nameof(patch))); + } + + public void AddFinalPatch(Patch patch) + { + finalPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + } + + private IPass[] EnumeratePasses() + { + IPass[] result = new IPass[modPasses.Count * 3 + 3]; + + result[0] = firstPatches; + result[1] = legacyPatches; + + for (int i = 0; i < modPasses.Count; i++) + { + result[i * 3 + 2] = modPasses[i].beforePass; + result[i * 3 + 3] = modPasses[i].forPass; + result[i * 3 + 4] = modPasses[i].afterPass; + } + + result[result.Length - 1] = finalPatches; + + return result; + } + + private void EnsureMod(string mod) + { + if (mod == null) throw new ArgumentNullException(nameof(mod)); + if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); + if (!HasMod(mod)) throw new KeyNotFoundException($"Mod '{mod}' not found"); + } } } diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 72faea68..23ad13a9 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -61,6 +61,7 @@ + diff --git a/ModuleManagerTests/PassTest.cs b/ModuleManagerTests/PassTest.cs new file mode 100644 index 00000000..85300ef5 --- /dev/null +++ b/ModuleManagerTests/PassTest.cs @@ -0,0 +1,73 @@ +using System; +using Xunit; +using TestUtils; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class PassTest + { + private UrlDir.UrlFile file; + + public PassTest() + { + file = UrlBuilder.CreateFile("abc/def.cfg"); + } + + [Fact] + public void TestConstructor__NameNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new Pass(null); + }); + + Assert.Equal("name", ex.ParamName); + } + + [Fact] + public void TestConstructor__NameEmpty() + { + ArgumentException ex = Assert.Throws(delegate + { + new Pass(""); + }); + + Assert.Equal("can't be empty\r\nParameter name: name", ex.Message); + Assert.Equal("name", ex.ParamName); + } + + [Fact] + public void TestName() + { + Pass pass = new Pass(":NOTINAMILLIONYEARS"); + + Assert.Equal(":NOTINAMILLIONYEARS", pass.Name); + } + + [Fact] + public void Test__Add__Enumerator() + { + Patch[] patches = + { + CreatePatch(Command.Edit, new ConfigNode()), + CreatePatch(Command.Copy, new ConfigNode()), + CreatePatch(Command.Delete, new ConfigNode()), + }; + + Pass pass = new Pass("blah") + { + patches[0], + patches[1], + patches[2], + }; + + Assert.Equal(patches, pass); + } + + private Patch CreatePatch(Command command, ConfigNode node) + { + return new Patch(new UrlDir.UrlConfig(file, node), command, node); + } + } +} diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index 1370095a..5929ddf5 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -5,6 +5,7 @@ using UnityEngine; using TestUtils; using ModuleManager; +using ModuleManager.Collections; using ModuleManager.Extensions; using ModuleManager.Logging; using ModuleManager.Progress; @@ -18,7 +19,10 @@ public class PatchApplierTest private readonly string[] modList = new[] { "mod1", "mod2" }; private UrlDir databaseRoot; private UrlDir.UrlFile file; - private readonly PatchList patchList; + private readonly IPass pass1; + private readonly IPass pass2; + private readonly IPass pass3; + private readonly IPatchList patchList; private readonly PatchApplier patchApplier; public PatchApplierTest() @@ -27,7 +31,14 @@ public PatchApplierTest() progress = Substitute.For(); databaseRoot = UrlBuilder.CreateRoot(); file = UrlBuilder.CreateFile("abc/def.cfg", databaseRoot); - patchList = new PatchList(modList); + pass1 = Substitute.For(); + pass2 = Substitute.For(); + pass3 = Substitute.For(); + pass1.Name.Returns(":PASS1"); + pass2.Name.Returns(":PASS2"); + pass3.Name.Returns(":PASS3"); + patchList = Substitute.For(); + patchList.GetEnumerator().Returns(new ArrayEnumerator(pass1, pass2, pass3)); patchApplier = new PatchApplier(patchList, databaseRoot, progress, logger); } @@ -57,15 +68,21 @@ public void TestApplyPatches__Edit() { "pqr", "stw" }, }); - patchList.firstPatches.Add(patch1); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(1).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingUpdate(config2, patch1.urlConfig); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingUpdate(config2, patch1.urlConfig); + progress.Received(1).PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(3, allConfigs.Length); @@ -117,15 +134,21 @@ public void TestApplyPatches__Copy() { "ccc", "005" }, }); - patchList.firstPatches.Add(patch1); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(1).PatchApplied(); - progress.Received().ApplyingCopy(config1, patch1.urlConfig); - progress.Received().ApplyingCopy(config2, patch1.urlConfig); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().ApplyingCopy(config1, patch1.urlConfig); + progress.Received().ApplyingCopy(config2, patch1.urlConfig); + progress.Received(1).PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(5, allConfigs.Length); @@ -183,15 +206,21 @@ public void TestApplyPatches__Delete() Patch patch1 = CreatePatch(Command.Delete, new TestConfigNode("-PART")); - patchList.firstPatches.Add(patch1); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(1).PatchApplied(); - progress.Received().ApplyingDelete(config1, patch1.urlConfig); - progress.Received().ApplyingDelete(config2, patch1.urlConfig); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().ApplyingDelete(config1, patch1.urlConfig); + progress.Received().ApplyingDelete(config2, patch1.urlConfig); + progress.Received(1).PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); @@ -245,18 +274,24 @@ public void TestApplyPatches__Name() Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART[004]")); - patchList.firstPatches.Add(patch1); - patchList.firstPatches.Add(patch2); - patchList.firstPatches.Add(patch3); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1, patch2, patch3)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(3).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingCopy(config2, patch2.urlConfig); - progress.Received().ApplyingDelete(config3, patch3.urlConfig); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingCopy(config2, patch2.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingDelete(config3, patch3.urlConfig); + progress.Received().PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(4, allConfigs.Length); @@ -330,18 +365,24 @@ public void TestApplyPatches__Name__Wildcard() Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART[0*4]")); - patchList.firstPatches.Add(patch1); - patchList.firstPatches.Add(patch2); - patchList.firstPatches.Add(patch3); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1, patch2, patch3)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(3).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingCopy(config2, patch2.urlConfig); - progress.Received().ApplyingDelete(config3, patch3.urlConfig); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingCopy(config2, patch2.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingDelete(config3, patch3.urlConfig); + progress.Received().PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(4, allConfigs.Length); @@ -400,15 +441,21 @@ public void TestApplyPatches__Name__Or() { "ddd", "006" }, }); - patchList.firstPatches.Add(patch1); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(1).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingUpdate(config2, patch1.urlConfig); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingUpdate(config2, patch1.urlConfig); + progress.Received().PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(3, allConfigs.Length); @@ -488,30 +535,38 @@ public void TestApplyPatches__Order() { "jjj", "010" }, }); - patchList.firstPatches.Add(patch1); - patchList.legacyPatches.Add(patch2); - patchList.modPasses["mod1"].beforePatches.Add(patch3); - patchList.modPasses["mod1"].forPatches.Add(patch4); - patchList.modPasses["mod1"].afterPatches.Add(patch5); - patchList.modPasses["mod2"].beforePatches.Add(patch6); - patchList.modPasses["mod2"].forPatches.Add(patch7); - patchList.modPasses["mod2"].afterPatches.Add(patch8); - patchList.finalPatches.Add(patch9); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1, patch2, patch3)); + pass2.GetEnumerator().Returns(new ArrayEnumerator(patch4, patch5, patch6)); + pass3.GetEnumerator().Returns(new ArrayEnumerator(patch7, patch8, patch9)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(9).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingUpdate(config1, patch2.urlConfig); - progress.Received().ApplyingUpdate(config1, patch3.urlConfig); - progress.Received().ApplyingUpdate(config1, patch4.urlConfig); - progress.Received().ApplyingUpdate(config1, patch5.urlConfig); - progress.Received().ApplyingUpdate(config1, patch6.urlConfig); - progress.Received().ApplyingUpdate(config1, patch7.urlConfig); - progress.Received().ApplyingUpdate(config1, patch8.urlConfig); - progress.Received().ApplyingUpdate(config1, patch9.urlConfig); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch2.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch3.urlConfig); + progress.Received().PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + progress.Received().ApplyingUpdate(config1, patch4.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch5.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch6.urlConfig); + progress.Received().PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + progress.Received().ApplyingUpdate(config1, patch7.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch8.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingUpdate(config1, patch9.urlConfig); + progress.Received().PatchApplied(); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); @@ -574,18 +629,24 @@ public void TestApplyPatches__Constraints() Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART:HAS[#ccc[005]]")); - patchList.firstPatches.Add(patch1); - patchList.firstPatches.Add(patch2); - patchList.firstPatches.Add(patch3); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1, patch2, patch3)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(3).PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingCopy(config2, patch2.urlConfig); - progress.Received().ApplyingDelete(config3, patch3.urlConfig); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingCopy(config2, patch2.urlConfig); + progress.Received().PatchApplied(); + progress.Received().ApplyingDelete(config3, patch3.urlConfig); + progress.Received().PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(4, allConfigs.Length); @@ -633,16 +694,24 @@ public void TestApplyPatches__Loop() new ConfigNode("MM_PATCH_LOOP"), }); - patchList.firstPatches.Add(patch1); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); patchApplier.ApplyPatches(); EnsureNoErrors(); - progress.Received(1).PatchApplied(); - progress.Received(4).ApplyingUpdate(config1, patch1.urlConfig); - - logger.Received().Log(LogType.Log, "Looping on abc/def/@PART:HAS[~aaa[>10]] to abc/def/PART"); + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + logger.Received().Log(LogType.Log, "Looping on abc/def/@PART:HAS[~aaa[>10]] to abc/def/PART"); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().ApplyingUpdate(config1, patch1.urlConfig); + progress.Received().PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); @@ -658,55 +727,6 @@ public void TestApplyPatches__Loop() }, allConfigs[0].config); } - /* - [Fact] - public void TestApplyPatches__InvalidOperator() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "1" }, - }, file); - - UrlDir.UrlConfig patch1 = new UrlDir.UrlConfig(file, new ConfigNode("%PART")); - UrlDir.UrlConfig patch2 = new UrlDir.UrlConfig(file, new ConfigNode("|PART")); - UrlDir.UrlConfig patch3 = new UrlDir.UrlConfig(file, new ConfigNode("#PART")); - UrlDir.UrlConfig patch4 = new UrlDir.UrlConfig(file, new ConfigNode("*PART")); - UrlDir.UrlConfig patch5 = new UrlDir.UrlConfig(file, new ConfigNode("&PART")); - - patchList.firstPatches.Add(patch1); - patchList.firstPatches.Add(patch2); - patchList.firstPatches.Add(patch3); - patchList.firstPatches.Add(patch4); - patchList.firstPatches.Add(patch5); - - patchApplier.ApplyPatches(); - - progress.DidNotReceiveWithAnyArgs().Error(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - - logger.DidNotReceive().Log(LogType.Error, Arg.Any()); - logger.DidNotReceiveWithAnyArgs().Exception(null, null); - - logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/%PART"); - logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/|PART"); - logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/#PART"); - logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/*PART"); - logger.Received().Log(LogType.Warning, "Invalid command encountered on a patch: abc/def/&PART"); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(1, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "1" }, - }, allConfigs[0].config); - - } - */ - [Fact] public void TestApplyPatches__Copy__NameNotChanged() { @@ -722,21 +742,27 @@ public void TestApplyPatches__Copy__NameNotChanged() { "bbb", "012" }, }); - patchList.firstPatches.Add(patch1); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); patchApplier.ApplyPatches(); progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.Received().Error(patch1.urlConfig, "Error - when applying copy abc/def/+PART to abc/def/PART - the copy needs to have a different name than the parent (use @name = xxx)"); - - logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); - logger.DidNotReceive().Log(LogType.Error, Arg.Any()); logger.DidNotReceiveWithAnyArgs().Exception(null, null); - - progress.Received(1).PatchApplied(); + + progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); + progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); + + Received.InOrder(delegate + { + logger.Received().Log(LogType.Log, ":PASS1 pass"); + progress.Received().Error(patch1.urlConfig, "Error - when applying copy abc/def/+PART to abc/def/PART - the copy needs to have a different name than the parent (use @name = xxx)"); + progress.Received().PatchApplied(); + logger.Received().Log(LogType.Log, ":PASS2 pass"); + logger.Received().Log(LogType.Log, ":PASS3 pass"); + }); UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); Assert.Equal(1, allConfigs.Length); diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 3f9005b1..7871f5e8 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -4,6 +4,7 @@ using NSubstitute; using TestUtils; using ModuleManager; +using ModuleManager.Logging; using ModuleManager.Progress; namespace ModuleManagerTests @@ -14,346 +15,640 @@ public class PatchExtractorTest private UrlDir.UrlFile file; private IPatchProgress progress; + private IPatchList patchList; + private IBasicLogger logger; + private PatchExtractor extractor; public PatchExtractorTest() { root = UrlBuilder.CreateRoot(); file = UrlBuilder.CreateFile("abc/def.cfg", root); + patchList = Substitute.For(); progress = Substitute.For(); + logger = Substitute.For(); + extractor = new PatchExtractor(patchList, progress, logger); } [Fact] - public void TestSortAndExtractPatches() + public void TestConstructor__PatchListNull() { - UrlDir.UrlConfig[] insertConfigs = + ArgumentNullException ex = Assert.Throws(delegate { - CreateConfig("NODE"), - CreateConfig("NADE"), - }; + new PatchExtractor(null, progress, logger); + }); - UrlDir.UrlConfig[] legacyConfigs = - { - CreateConfig("@NODE"), - CreateConfig("@NADE[foo]:HAS[#bar]"), - }; + Assert.Equal("patchList", ex.ParamName); + } - UrlDir.UrlConfig[] firstConfigs = + [Fact] + public void TestConstructor__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate { - CreateConfig("@NODE:FIRST"), - CreateConfig("@NODE[foo]:HAS[#bar]:FIRST"), - CreateConfig("@NADE:First"), - CreateConfig("@NADE:first"), - }; + new PatchExtractor(patchList, null, logger); + }); - UrlDir.UrlConfig[] finalConfigs = - { - CreateConfig("@NODE:FINAL"), - CreateConfig("@NODE[foo]:HAS[#bar]:FINAL"), - CreateConfig("@NADE:Final"), - CreateConfig("@NADE:final"), - }; + Assert.Equal("progress", ex.ParamName); + } - UrlDir.UrlConfig[] beforeMod1Configs = + [Fact] + public void TestConstructor__LoggerNull() + { + ArgumentNullException ex = Assert.Throws(delegate { - CreateConfig("@NODE:BEFORE[mod1]"), - CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod1]"), - CreateConfig("@NADE:before[mod1]"), - CreateConfig("@NADE:BEFORE[MOD1]"), - }; + new PatchExtractor(patchList, progress, null); + }); + + Assert.Equal("logger", ex.ParamName); + } + + [Fact] + public void TestExtractPatch__Insert() + { + UrlDir.UrlConfig patchConfig = CreateConfig("NODE"); + + extractor.ExtractPatch(patchConfig); + + AssertNoErrors(); + + Assert.Equal(new[] { patchConfig }, root.AllConfigs); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + progress.DidNotReceive().PatchAdded(); + + EnsureNeedsSatisfied(); + } + + [Fact] + public void TestExtractPatch__First() + { + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:FIRST"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FIRST"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:First"); + UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:first"); + + List patches = new List(); + patchList.AddFirstPatch(Arg.Do(patch => patches.Add(patch))); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); + extractor.ExtractPatch(patchConfig4); + + AssertNoErrors(); + + Assert.Empty(root.AllConfigs); + + Assert.Equal(4, patches.Count); + AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); + AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); + AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); - UrlDir.UrlConfig[] forMod1Configs = + Received.InOrder(delegate { - CreateConfig("@NODE:FOR[mod1]"), - CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod1]"), - CreateConfig("@NADE:for[mod1]"), - CreateConfig("@NADE:FOR[MOD1]"), - }; + patchList.Received().AddFirstPatch(patches[0]); + progress.Received().PatchAdded(); + patchList.Received().AddFirstPatch(patches[1]); + progress.Received().PatchAdded(); + patchList.Received().AddFirstPatch(patches[2]); + progress.Received().PatchAdded(); + patchList.Received().AddFirstPatch(patches[3]); + progress.Received().PatchAdded(); + }); + + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + EnsureNeedsSatisfied(); + } + + [Fact] + public void TestExtractPatch__Legacy() + { + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE"); + + List patches = new List(); + patchList.AddLegacyPatch(Arg.Do(patch => patches.Add(patch))); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + + AssertNoErrors(); + + Assert.Empty(root.AllConfigs); + + Assert.Equal(2, patches.Count); + AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); - UrlDir.UrlConfig[] afterMod1Configs = + Received.InOrder(delegate { - CreateConfig("@NODE:AFTER[mod1]"), - CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod1]"), - CreateConfig("@NADE:after[mod1]"), - CreateConfig("@NADE:AFTER[MOD1]"), - }; + patchList.Received().AddLegacyPatch(patches[0]); + progress.Received().PatchAdded(); + patchList.Received().AddLegacyPatch(patches[1]); + progress.Received().PatchAdded(); + }); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + EnsureNeedsSatisfied(); + } + + [Fact] + public void TestExtractPatch__BeforeMod() + { + patchList.HasMod("mod1").Returns(true); + + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod1]"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:BEFORE[mod1]"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:Before[mod1]"); + UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:before[MOD1]"); + + List patches = new List(); + patchList.AddBeforePatch("mod1", Arg.Do(patch => patches.Add(patch))); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); + extractor.ExtractPatch(patchConfig4); + + AssertNoErrors(); + + Assert.Empty(root.AllConfigs); + + Assert.Equal(4, patches.Count); + AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); + AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); + AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); - UrlDir.UrlConfig[] beforeMod2Configs = + Received.InOrder(delegate { - CreateConfig("@NODE:BEFORE[mod2]"), - CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod2]"), - CreateConfig("@NADE:before[mod2]"), - CreateConfig("@NADE:BEFORE[MOD2]"), - }; + patchList.Received().AddBeforePatch("mod1", patches[0]); + progress.Received().PatchAdded(); + patchList.Received().AddBeforePatch("mod1", patches[1]); + progress.Received().PatchAdded(); + patchList.Received().AddBeforePatch("mod1", patches[2]); + progress.Received().PatchAdded(); + patchList.Received().AddBeforePatch("mod1", patches[3]); + progress.Received().PatchAdded(); + }); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + EnsureNeedsSatisfied(); + } + + [Fact] + public void TestExtractPatch__BeforeMod__ModDoesNotExist() + { + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod3]"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:BEFORE[mod3]"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:Before[mod3]"); + UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:before[MOD3]"); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); + extractor.ExtractPatch(patchConfig4); - UrlDir.UrlConfig[] forMod2Configs = + AssertNoErrors(); + + Assert.Empty(root.AllConfigs); + + Received.InOrder(delegate { - CreateConfig("@NODE:FOR[mod2]"), - CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod2]"), - CreateConfig("@NADE:for[mod2]"), - CreateConfig("@NADE:FOR[MOD2]"), - }; + progress.Received().NeedsUnsatisfiedBefore(patchConfig1); + progress.Received().NeedsUnsatisfiedBefore(patchConfig2); + progress.Received().NeedsUnsatisfiedBefore(patchConfig3); + progress.Received().NeedsUnsatisfiedBefore(patchConfig4); + }); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - UrlDir.UrlConfig[] afterMod2Configs = + progress.DidNotReceive().PatchAdded(); + } + + [Fact] + public void TestExtractPatch__ForMod() + { + patchList.HasMod("mod1").Returns(true); + + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod1]"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FOR[mod1]"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:For[mod1]"); + UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:for[MOD1]"); + + List patches = new List(); + patchList.AddForPatch("mod1", Arg.Do(patch => patches.Add(patch))); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); + extractor.ExtractPatch(patchConfig4); + + AssertNoErrors(); + + Assert.Empty(root.AllConfigs); + + Assert.Equal(4, patches.Count); + AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); + AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); + AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); + + + Received.InOrder(delegate { - CreateConfig("@NODE:AFTER[mod2]"), - CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod2]"), - CreateConfig("@NADE:after[mod2]"), - CreateConfig("@NADE:AFTER[MOD2]"), - }; + patchList.Received().AddForPatch("mod1", patches[0]); + progress.Received().PatchAdded(); + patchList.Received().AddForPatch("mod1", patches[1]); + progress.Received().PatchAdded(); + patchList.Received().AddForPatch("mod1", patches[2]); + progress.Received().PatchAdded(); + patchList.Received().AddForPatch("mod1", patches[3]); + progress.Received().PatchAdded(); + }); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + EnsureNeedsSatisfied(); + } + + [Fact] + public void TestExtractPatch__ForMod__ModDoesNotExist() + { + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod3]"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FOR[mod3]"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:For[mod3]"); + UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:for[MOD3]"); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); + extractor.ExtractPatch(patchConfig4); + + AssertNoErrors(); + + Assert.Empty(root.AllConfigs); - UrlDir.UrlConfig[] beforeMod3Configs = + Received.InOrder(delegate { - CreateConfig("@NODE:BEFORE[mod3]"), - CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod3]"), - CreateConfig("@NADE:before[mod3]"), - CreateConfig("@NADE:BEFORE[MOD3]"), - }; + progress.Received().NeedsUnsatisfiedFor(patchConfig1); + progress.Received().NeedsUnsatisfiedFor(patchConfig2); + progress.Received().NeedsUnsatisfiedFor(patchConfig3); + progress.Received().NeedsUnsatisfiedFor(patchConfig4); + }); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + progress.DidNotReceive().PatchAdded(); + } + + [Fact] + public void TestExtractPatch__AfterMod() + { + patchList.HasMod("mod1").Returns(true); + + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod1]"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:AFTER[mod1]"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:After[mod1]"); + UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:after[MOD1]"); + + List patches = new List(); + patchList.AddAfterPatch("mod1", Arg.Do(patch => patches.Add(patch))); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); + extractor.ExtractPatch(patchConfig4); + + AssertNoErrors(); + + Assert.Equal(4, patches.Count); + AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); + AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); + AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); - UrlDir.UrlConfig[] forMod3Configs = + Assert.Empty(root.AllConfigs); + + Received.InOrder(delegate { - CreateConfig("@NODE:FOR[mod3]"), - CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod3]"), - CreateConfig("@NADE:for[mod3]"), - CreateConfig("@NADE:FOR[MOD3]"), - }; + patchList.Received().AddAfterPatch("mod1", patches[0]); + progress.Received().PatchAdded(); + patchList.Received().AddAfterPatch("mod1", patches[1]); + progress.Received().PatchAdded(); + patchList.Received().AddAfterPatch("mod1", patches[2]); + progress.Received().PatchAdded(); + patchList.Received().AddAfterPatch("mod1", patches[3]); + progress.Received().PatchAdded(); + }); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + EnsureNeedsSatisfied(); + } + + [Fact] + public void TestExtractPatch__AfterMod__ModDoesNotExist() + { + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod3]"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:AFTER[mod3]"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:After[mod3]"); + UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:after[MOD3]"); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); + extractor.ExtractPatch(patchConfig4); + + AssertNoErrors(); + + Assert.Empty(root.AllConfigs); - UrlDir.UrlConfig[] afterMod3Configs = + Received.InOrder(delegate { - CreateConfig("@NODE:AFTER[mod3]"), - CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod3]"), - CreateConfig("@NADE:after[mod3]"), - CreateConfig("@NADE:AFTER[MOD3]"), - }; + progress.Received().NeedsUnsatisfiedAfter(patchConfig1); + progress.Received().NeedsUnsatisfiedAfter(patchConfig2); + progress.Received().NeedsUnsatisfiedAfter(patchConfig3); + progress.Received().NeedsUnsatisfiedAfter(patchConfig4); + }); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - string[] modList = { "mod1", "mod2" }; - PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + progress.DidNotReceive().PatchAdded(); + } - progress.DidNotReceiveWithAnyArgs().Error(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + [Fact] + public void TestExtractPatch__Final() + { + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:FINAL"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FINAL"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:Final"); + UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:final"); + + List patches = new List(); + patchList.AddFinalPatch(Arg.Do(patch => patches.Add(patch))); + + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); + extractor.ExtractPatch(patchConfig4); + + AssertNoErrors(); - Assert.True(list.modPasses.HasMod("mod1")); - Assert.True(list.modPasses.HasMod("mod2")); - Assert.False(list.modPasses.HasMod("mod3")); - - Assert.Equal(insertConfigs, root.AllConfigs); - - List currentPatches; - - currentPatches = list.legacyPatches; - Assert.Equal(legacyConfigs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], legacyConfigs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], legacyConfigs[1], Command.Edit, "NADE[foo]:HAS[#bar]"); - - currentPatches = list.firstPatches; - Assert.Equal(firstConfigs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], firstConfigs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], firstConfigs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(currentPatches[2], firstConfigs[2], Command.Edit, "NADE"); - AssertPatchCorrect(currentPatches[3], firstConfigs[3], Command.Edit, "NADE"); - - currentPatches = list.finalPatches; - Assert.Equal(finalConfigs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], finalConfigs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], finalConfigs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(currentPatches[2], finalConfigs[2], Command.Edit, "NADE"); - AssertPatchCorrect(currentPatches[3], finalConfigs[3], Command.Edit, "NADE"); - - currentPatches = list.modPasses["mod1"].beforePatches; - Assert.Equal(beforeMod1Configs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], beforeMod1Configs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], beforeMod1Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(currentPatches[2], beforeMod1Configs[2], Command.Edit, "NADE"); - AssertPatchCorrect(currentPatches[3], beforeMod1Configs[3], Command.Edit, "NADE"); - - currentPatches = list.modPasses["mod1"].forPatches; - Assert.Equal(forMod1Configs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], forMod1Configs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], forMod1Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(currentPatches[2], forMod1Configs[2], Command.Edit, "NADE"); - AssertPatchCorrect(currentPatches[3], forMod1Configs[3], Command.Edit, "NADE"); - - currentPatches = list.modPasses["mod1"].afterPatches; - Assert.Equal(afterMod1Configs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], afterMod1Configs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], afterMod1Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(currentPatches[2], afterMod1Configs[2], Command.Edit, "NADE"); - AssertPatchCorrect(currentPatches[3], afterMod1Configs[3], Command.Edit, "NADE"); - - currentPatches = list.modPasses["mod2"].beforePatches; - Assert.Equal(beforeMod2Configs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], beforeMod2Configs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], beforeMod2Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(currentPatches[2], beforeMod2Configs[2], Command.Edit, "NADE"); - AssertPatchCorrect(currentPatches[3], beforeMod2Configs[3], Command.Edit, "NADE"); - - currentPatches = list.modPasses["mod2"].forPatches; - Assert.Equal(forMod2Configs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], forMod2Configs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], forMod2Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(currentPatches[2], forMod2Configs[2], Command.Edit, "NADE"); - AssertPatchCorrect(currentPatches[3], forMod2Configs[3], Command.Edit, "NADE"); - - currentPatches = list.modPasses["mod2"].afterPatches; - Assert.Equal(afterMod2Configs.Length, currentPatches.Count); - AssertPatchCorrect(currentPatches[0], afterMod2Configs[0], Command.Edit, "NODE"); - AssertPatchCorrect(currentPatches[1], afterMod2Configs[1], Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(currentPatches[2], afterMod2Configs[2], Command.Edit, "NADE"); - AssertPatchCorrect(currentPatches[3], afterMod2Configs[3], Command.Edit, "NADE"); - - progress.Received(34).PatchAdded(); - - progress.Received().NeedsUnsatisfiedBefore(beforeMod3Configs[0]); - progress.Received().NeedsUnsatisfiedBefore(beforeMod3Configs[1]); - progress.Received().NeedsUnsatisfiedBefore(beforeMod3Configs[2]); - progress.Received().NeedsUnsatisfiedBefore(beforeMod3Configs[3]); - - progress.Received().NeedsUnsatisfiedFor(forMod3Configs[0]); - progress.Received().NeedsUnsatisfiedFor(forMod3Configs[1]); - progress.Received().NeedsUnsatisfiedFor(forMod3Configs[2]); - progress.Received().NeedsUnsatisfiedFor(forMod3Configs[3]); - - progress.Received().NeedsUnsatisfiedAfter(afterMod3Configs[0]); - progress.Received().NeedsUnsatisfiedAfter(afterMod3Configs[1]); - progress.Received().NeedsUnsatisfiedAfter(afterMod3Configs[2]); - progress.Received().NeedsUnsatisfiedAfter(afterMod3Configs[3]); + Assert.Empty(root.AllConfigs); + + Assert.Equal(4, patches.Count); + AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); + AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); + AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); + AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); + + Received.InOrder(delegate + { + patchList.Received().AddFinalPatch(patches[0]); + progress.Received().PatchAdded(); + patchList.Received().AddFinalPatch(patches[1]); + progress.Received().PatchAdded(); + patchList.Received().AddFinalPatch(patches[2]); + progress.Received().PatchAdded(); + patchList.Received().AddFinalPatch(patches[3]); + progress.Received().PatchAdded(); + }); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + + EnsureNeedsSatisfied(); } [Fact] - public void TestSortAndExtractPatches__InsertWithPass() + public void TestExtractPatch__InsertWithPass() { - UrlDir.UrlConfig config1 = CreateConfig("NODE"); - UrlDir.UrlConfig config2 = CreateConfig("NODE:FOR[mod1]"); - UrlDir.UrlConfig config3 = CreateConfig("NODE:FOR[mod2]"); - UrlDir.UrlConfig config4 = CreateConfig("NODE:FINAL"); + UrlDir.UrlConfig patchConfig1 = CreateConfig("NODE:FOR[mod1]"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("NODE:FOR[mod2]"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("NODE:FINAL"); - string[] modList = { "mod1" }; - PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); - Assert.Equal(new[] { config1 }, root.AllConfigs); + Assert.Empty(root.AllConfigs); - progress.Received().Error(config2, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FOR[mod1]"); - progress.Received().Error(config3, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FOR[mod2]"); - progress.Received().Error(config4, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FINAL"); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - Assert.Empty(list.firstPatches); - Assert.Empty(list.legacyPatches); - Assert.Empty(list.finalPatches); - Assert.Empty(list.modPasses["mod1"].beforePatches); - Assert.Empty(list.modPasses["mod1"].forPatches); - Assert.Empty(list.modPasses["mod1"].afterPatches); + Received.InOrder(delegate + { + progress.Received().Error(patchConfig1, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FOR[mod1]"); + progress.Received().Error(patchConfig2, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FOR[mod2]"); + progress.Received().Error(patchConfig3, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FINAL"); + }); + + EnsureNeedsSatisfied(); progress.DidNotReceive().PatchAdded(); } [Fact] - public void TestSortAndExtractPatches__MoreThanOnePass() + public void TestExtractPatch__MoreThanOnePass() { - UrlDir.UrlConfig config1 = CreateConfig("@NODE:FIRST"); - UrlDir.UrlConfig config2 = CreateConfig("@NODE:FIRST:FIRST"); - UrlDir.UrlConfig config3 = CreateConfig("@NODE:FIRST:FOR[mod1]"); + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE:FIRST:FIRST"); + UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FIRST:FOR[mod1]"); + UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:FOR[mod1]:AFTER[mod2]"); - string[] modList = { "mod1" }; - PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + extractor.ExtractPatch(patchConfig1); + extractor.ExtractPatch(patchConfig2); + extractor.ExtractPatch(patchConfig3); Assert.Empty(root.AllConfigs); - progress.Received().Error(config2, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FIRST"); - progress.Received().Error(config3, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FOR[mod1]"); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + Received.InOrder(delegate + { + progress.Received().Error(patchConfig1, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FIRST"); + progress.Received().Error(patchConfig2, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FOR[mod1]"); + progress.Received().Error(patchConfig3, "Error - more than one pass specifier on a node: abc/def/@NODE:FOR[mod1]:AFTER[mod2]"); + }); - Assert.Equal(1, list.firstPatches.Count); - AssertPatchCorrect(list.firstPatches[0], config1, Command.Edit, "NODE"); - Assert.Empty(list.legacyPatches); - Assert.Empty(list.finalPatches); - Assert.Empty(list.modPasses["mod1"].beforePatches); - Assert.Empty(list.modPasses["mod1"].forPatches); - Assert.Empty(list.modPasses["mod1"].afterPatches); + EnsureNeedsSatisfied(); - progress.Received(1).PatchAdded(); + progress.DidNotReceive().PatchAdded(); } [Fact] - public void TestSortAndExtractPatches__Exception() + public void TestExtractPatch__Exception() { Exception e = new Exception("an exception was thrown"); progress.WhenForAnyArgs(p => p.Error(null, null)).Throw(e); - UrlDir.UrlConfig config1 = CreateConfig("@NODE"); - UrlDir.UrlConfig config2 = CreateConfig("@NODE:FIRST:FIRST"); - UrlDir.UrlConfig config3 = CreateConfig("@NADE:FIRST"); + UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE:FIRST:FIRST"); + + extractor.ExtractPatch(patchConfig1); - string[] modList = { "mod1" }; - PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + Assert.Empty(root.AllConfigs); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.Received().Exception(config2, "Exception while parsing pass for config: abc/def/@NODE:FIRST:FIRST", e); + progress.Received().Exception(patchConfig1, "Exception while parsing pass for config: abc/def/@NODE:FIRST:FIRST", e); - Assert.Equal(1, list.legacyPatches.Count); - AssertPatchCorrect(list.legacyPatches[0], config1, Command.Edit, "NODE"); - Assert.Equal(1, list.firstPatches.Count); - AssertPatchCorrect(list.firstPatches[0], config3, Command.Edit, "NADE"); + EnsureNeedsSatisfied(); - progress.Received(2).PatchAdded(); + progress.DidNotReceive().PatchAdded(); } [Fact] - public void TestSortAndExtractPatches__NotBracketBalanced() + public void TestExtractPatch__NotBracketBalanced() { - UrlDir.UrlConfig config1 = CreateConfig("@NODE:FOR[mod1]"); - UrlDir.UrlConfig config2 = CreateConfig("@NODE:FOR["); - UrlDir.UrlConfig config3 = CreateConfig("NODE:HAS[#foo[]"); + UrlDir.UrlConfig config1 = CreateConfig("@NODE:FOR["); + UrlDir.UrlConfig config2 = CreateConfig("NODE:HAS[#foo[]"); - string[] modList = { "mod1" }; - PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + extractor.ExtractPatch(config1); + extractor.ExtractPatch(config2); Assert.Empty(root.AllConfigs); - progress.Received().Error(config2, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\nabc/def/@NODE:FOR["); - progress.Received().Error(config3, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\nabc/def/NODE:HAS[#foo[]"); - - Assert.Empty(list.firstPatches); - Assert.Empty(list.legacyPatches); - Assert.Empty(list.finalPatches); - Assert.Empty(list.modPasses["mod1"].beforePatches); - Assert.Equal(1, list.modPasses["mod1"].forPatches.Count); - AssertPatchCorrect(list.modPasses["mod1"].forPatches[0], config1, Command.Edit, "NODE"); - Assert.Empty(list.modPasses["mod1"].afterPatches); - - progress.Received(1).PatchAdded(); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + Received.InOrder(delegate + { + progress.Received().Error(config1, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\nabc/def/@NODE:FOR["); + progress.Received().Error(config2, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\nabc/def/NODE:HAS[#foo[]"); + }); + + EnsureNeedsSatisfied(); + + progress.DidNotReceive().PatchAdded(); } [Fact] - public void TestSortAndExtractPatches__BadlyFormed() + public void TestExtractPatch__BadlyFormed() { - UrlDir.UrlConfig config1 = CreateConfig("@NODE:FOR[mod1]"); - UrlDir.UrlConfig config2 = CreateConfig("@NODE:FOR[]"); - UrlDir.UrlConfig config3 = CreateConfig("@NADE:FIRST:BEFORE"); - UrlDir.UrlConfig config4 = CreateConfig("@NADE:AFTER"); - - string[] modList = { "mod1" }; - PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + UrlDir.UrlConfig config1 = CreateConfig("@NODE[foo]:HAS[#bar]:FOR[]"); + UrlDir.UrlConfig config2 = CreateConfig("@NODE:BEFORE"); + UrlDir.UrlConfig config3 = CreateConfig("@NODE:AFTER"); + + extractor.ExtractPatch(config1); + extractor.ExtractPatch(config2); + extractor.ExtractPatch(config3); Assert.Empty(root.AllConfigs); - progress.Received().Error(config2, "Error - malformed :FOR patch specifier detected: abc/def/@NODE:FOR[]"); - progress.Received().Error(config3, "Error - more than one pass specifier on a node: abc/def/@NADE:FIRST:BEFORE"); - progress.Received().Error(config3, "Error - malformed :BEFORE patch specifier detected: abc/def/@NADE:FIRST:BEFORE"); - progress.Received().Error(config4, "Error - malformed :AFTER patch specifier detected: abc/def/@NADE:AFTER"); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + + Received.InOrder(delegate + { + progress.Received().Error(config1, "Error - malformed :FOR patch specifier detected: abc/def/@NODE[foo]:HAS[#bar]:FOR[]"); + progress.Received().Error(config2, "Error - malformed :BEFORE patch specifier detected: abc/def/@NODE:BEFORE"); + progress.Received().Error(config3, "Error - malformed :AFTER patch specifier detected: abc/def/@NODE:AFTER"); + }); - Assert.Empty(list.firstPatches); - Assert.Empty(list.legacyPatches); - Assert.Empty(list.finalPatches); - Assert.Empty(list.modPasses["mod1"].beforePatches); - Assert.Equal(1, list.modPasses["mod1"].forPatches.Count); - AssertPatchCorrect(list.modPasses["mod1"].forPatches[0], config1, Command.Edit, "NODE"); - Assert.Empty(list.modPasses["mod1"].afterPatches); + EnsureNeedsSatisfied(); - progress.Received(1).PatchAdded(); + progress.DidNotReceive().PatchAdded(); } [Fact] - public void TestSortAndExtractPatches__Command() + public void TestExtractPatch__Command() { + patchList.HasMod("mod1").Returns(true); + UrlDir.UrlConfig config01 = CreateConfig("@NODE:FOR[mod1]"); UrlDir.UrlConfig config02 = CreateConfig("+NODE:FOR[mod1]"); UrlDir.UrlConfig config03 = CreateConfig("$NODE:FOR[mod1]"); @@ -365,30 +660,59 @@ public void TestSortAndExtractPatches__Command() UrlDir.UrlConfig config09 = CreateConfig("#NODE:FOR[mod1]"); UrlDir.UrlConfig config10 = CreateConfig("*NODE:FOR[mod1]"); - string[] modList = { "mod1" }; - PatchList list = PatchExtractor.SortAndExtractPatches(root, modList, progress); + List patches = new List(); + patchList.AddForPatch("mod1", Arg.Do(patch => patches.Add(patch))); + + extractor.ExtractPatch(config01); + extractor.ExtractPatch(config02); + extractor.ExtractPatch(config03); + extractor.ExtractPatch(config04); + extractor.ExtractPatch(config05); + extractor.ExtractPatch(config06); + extractor.ExtractPatch(config07); + extractor.ExtractPatch(config08); + extractor.ExtractPatch(config09); + extractor.ExtractPatch(config10); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); Assert.Empty(root.AllConfigs); - progress.Received().Error(config06, "Error - replace command (%) is not valid on a root node: abc/def/%NODE:FOR[mod1]"); - progress.Received().Error(config07, "Error - create command (&) is not valid on a root node: abc/def/&NODE:FOR[mod1]"); - progress.Received().Error(config08, "Error - rename command (|) is not valid on a root node: abc/def/|NODE:FOR[mod1]"); - progress.Received().Error(config09, "Error - paste command (#) is not valid on a root node: abc/def/#NODE:FOR[mod1]"); - progress.Received().Error(config10, "Error - special command (*) is not valid on a root node: abc/def/*NODE:FOR[mod1]"); - - Assert.Empty(list.firstPatches); - Assert.Empty(list.legacyPatches); - Assert.Empty(list.finalPatches); - Assert.Empty(list.modPasses["mod1"].beforePatches); - Assert.Equal(5, list.modPasses["mod1"].forPatches.Count); - AssertPatchCorrect(list.modPasses["mod1"].forPatches[0], config01, Command.Edit, "NODE"); - AssertPatchCorrect(list.modPasses["mod1"].forPatches[1], config02, Command.Copy, "NODE"); - AssertPatchCorrect(list.modPasses["mod1"].forPatches[2], config03, Command.Copy, "NODE"); - AssertPatchCorrect(list.modPasses["mod1"].forPatches[3], config04, Command.Delete, "NODE"); - AssertPatchCorrect(list.modPasses["mod1"].forPatches[4], config05, Command.Delete, "NODE"); - Assert.Empty(list.modPasses["mod1"].afterPatches); - - progress.Received(5).PatchAdded(); + Assert.Equal(5, patches.Count); + AssertPatchCorrect(patches[0], config01, Command.Edit, "NODE"); + AssertPatchCorrect(patches[1], config02, Command.Copy, "NODE"); + AssertPatchCorrect(patches[2], config03, Command.Copy, "NODE"); + AssertPatchCorrect(patches[3], config04, Command.Delete, "NODE"); + AssertPatchCorrect(patches[4], config05, Command.Delete, "NODE"); + progress.Received().PatchAdded(); + + Received.InOrder(delegate + { + patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[0]); + progress.Received().PatchAdded(); + patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[1]); + progress.Received().PatchAdded(); + patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[2]); + progress.Received().PatchAdded(); + patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[3]); + progress.Received().PatchAdded(); + patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[4]); + progress.Received().PatchAdded(); + progress.Received().Error(config06, "Error - replace command (%) is not valid on a root node: abc/def/%NODE:FOR[mod1]"); + progress.Received().Error(config07, "Error - create command (&) is not valid on a root node: abc/def/&NODE:FOR[mod1]"); + progress.Received().Error(config08, "Error - rename command (|) is not valid on a root node: abc/def/|NODE:FOR[mod1]"); + progress.Received().Error(config09, "Error - paste command (#) is not valid on a root node: abc/def/#NODE:FOR[mod1]"); + progress.Received().Error(config10, "Error - special command (*) is not valid on a root node: abc/def/*NODE:FOR[mod1]"); + }); + + EnsureNeedsSatisfied(); + + patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); + patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); + patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); } private UrlDir.UrlConfig CreateConfig(string name) @@ -410,6 +734,7 @@ private UrlDir.UrlConfig CreateConfig(string name) private void AssertPatchCorrect(Patch patch, UrlDir.UrlConfig originalUrl, Command expectedCommand, string expectedNodeName) { Assert.Same(originalUrl, patch.urlConfig); + Assert.Equal(expectedCommand, patch.command); Assert.Equal(expectedNodeName, patch.node.name); ConfigNode originalNode = originalUrl.config; @@ -427,6 +752,21 @@ private void AssertPatchCorrect(Patch patch, UrlDir.UrlConfig originalUrl, Comma { Assert.Same(originalNode.nodes[i], patch.node.nodes[i]); } + + } + + private void AssertNoErrors() + { + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + + private void EnsureNeedsSatisfied() + { + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); } } } diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index dc19b426..ce252c13 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -1,86 +1,304 @@ using System; using System.Collections.Generic; +using System.Linq; using Xunit; +using TestUtils; using ModuleManager; namespace ModuleManagerTests { public class PatchListTest { + private UrlDir databaseRoot; + private UrlDir.UrlFile file; + private PatchList patchList; + + public PatchListTest() + { + databaseRoot = UrlBuilder.CreateRoot(); + file = UrlBuilder.CreateFile("abc/def.cfg", databaseRoot); + + patchList = new PatchList(new[] { "mod1", "mod2" }); + } + + [Fact] + public void Test__Lifecycle() + { + Patch patch01 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch02 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch03 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch04 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch05 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch06 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch07 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch08 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch09 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch10 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch11 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch12 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch13 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch14 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch15 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch16 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch17 = CreatePatch(Command.Edit, new ConfigNode("blah")); + Patch patch18 = CreatePatch(Command.Edit, new ConfigNode("blah")); + + patchList.AddFirstPatch(patch01); + patchList.AddFirstPatch(patch02); + patchList.AddLegacyPatch(patch03); + patchList.AddLegacyPatch(patch04); + patchList.AddBeforePatch("mod1", patch05); + patchList.AddBeforePatch("MOD1", patch06); + patchList.AddForPatch("mod1", patch07); + patchList.AddForPatch("MOD1", patch08); + patchList.AddAfterPatch("mod1", patch09); + patchList.AddAfterPatch("MOD1", patch10); + patchList.AddBeforePatch("mod2", patch11); + patchList.AddBeforePatch("MOD2", patch12); + patchList.AddForPatch("mod2", patch13); + patchList.AddForPatch("MOD2", patch14); + patchList.AddAfterPatch("mod2", patch15); + patchList.AddAfterPatch("MOD2", patch16); + patchList.AddFinalPatch(patch17); + patchList.AddFinalPatch(patch18); + + IPass[] passes = patchList.ToArray(); + + Assert.Equal(":FIRST", passes[0].Name); + Assert.Equal(new[] { patch01, patch02 }, passes[0]); + + Assert.Equal(":LEGACY (default)", passes[1].Name); + Assert.Equal(new[] { patch03, patch04 }, passes[1]); + + Assert.Equal(":BEFORE[MOD1]", passes[2].Name); + Assert.Equal(new[] { patch05, patch06 }, passes[2]); + + Assert.Equal(":FOR[MOD1]", passes[3].Name); + Assert.Equal(new[] { patch07, patch08 }, passes[3]); + + Assert.Equal(":AFTER[MOD1]", passes[4].Name); + Assert.Equal(new[] { patch09, patch10 }, passes[4]); + + Assert.Equal(":BEFORE[MOD2]", passes[5].Name); + Assert.Equal(new[] { patch11, patch12 }, passes[5]); + + Assert.Equal(":FOR[MOD2]", passes[6].Name); + Assert.Equal(new[] { patch13, patch14 }, passes[6]); + + Assert.Equal(":AFTER[MOD2]", passes[7].Name); + Assert.Equal(new[] { patch15, patch16 }, passes[7]); + + Assert.Equal(":FINAL", passes[8].Name); + Assert.Equal(new[] { patch17, patch18 }, passes[8]); + } + + [Fact] + public void TestHasMod__True() + { + patchList = new PatchList(new[] { "mod1", "Mod2", "MOD3" }); + + Assert.True(patchList.HasMod("mod1")); + Assert.True(patchList.HasMod("Mod1")); + Assert.True(patchList.HasMod("MOD1")); + Assert.True(patchList.HasMod("mod2")); + Assert.True(patchList.HasMod("Mod2")); + Assert.True(patchList.HasMod("MOD2")); + Assert.True(patchList.HasMod("mod3")); + Assert.True(patchList.HasMod("Mod3")); + Assert.True(patchList.HasMod("MOD3")); + } + + [Fact] + public void TestHasMod__False() + { + Assert.False(patchList.HasMod("mod3")); + Assert.False(patchList.HasMod("Mod3")); + Assert.False(patchList.HasMod("MOD3")); + } + + [Fact] + public void TestHasMod__Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + Assert.True(patchList.HasMod(null)); + }); + + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestHasMod__Blank() + { + ArgumentException ex = Assert.Throws(delegate + { + Assert.True(patchList.HasMod("")); + }); + + Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestAddLegacyPatch__Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + patchList.AddLegacyPatch(null); + }); + + Assert.Equal("patch", ex.ParamName); + } + + [Fact] + public void TestAddBeforePatch__ModNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + patchList.AddBeforePatch(null, CreatePatch(Command.Edit, new ConfigNode())); + }); + + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestAddBeforePatch__ModBlank() + { + ArgumentException ex = Assert.Throws(delegate + { + patchList.AddBeforePatch("", CreatePatch(Command.Edit, new ConfigNode())); + }); + + Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); + Assert.Equal("mod", ex.ParamName); + } + [Fact] - public void TestConstructor() + public void TestAddBeforePatch__PatchNull() { - PatchList list = new PatchList(new string[0]); + ArgumentNullException ex = Assert.Throws(delegate + { + patchList.AddBeforePatch("mod1", null); + }); - Assert.NotNull(list.firstPatches); - Assert.NotNull(list.legacyPatches); - Assert.NotNull(list.finalPatches); - Assert.NotNull(list.modPasses); + Assert.Equal("patch", ex.ParamName); } [Fact] - public void TestModPasses__HasMod() + public void TestAddBeforePatch__ModDoesNotExist() { - PatchList list = new PatchList(new[] { "mod1", "Mod2", "MOD3" }); + KeyNotFoundException ex = Assert.Throws(delegate + { + patchList.AddBeforePatch("mod3", CreatePatch(Command.Edit, new ConfigNode())); + }); - PatchList.ModPassCollection collection = list.modPasses; + Assert.Equal("Mod 'mod3' not found", ex.Message); + } - Assert.True(collection.HasMod("mod1")); - Assert.True(collection.HasMod("Mod1")); - Assert.True(collection.HasMod("MOD1")); + [Fact] + public void TestAddForPatch__ModNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + patchList.AddForPatch(null, CreatePatch(Command.Edit, new ConfigNode())); + }); - Assert.True(collection.HasMod("mod2")); - Assert.True(collection.HasMod("Mod2")); - Assert.True(collection.HasMod("MOD2")); + Assert.Equal("mod", ex.ParamName); + } - Assert.True(collection.HasMod("mod3")); - Assert.True(collection.HasMod("Mod3")); - Assert.True(collection.HasMod("MOD3")); + [Fact] + public void TestAddForPatch__ModBlank() + { + ArgumentException ex = Assert.Throws(delegate + { + patchList.AddForPatch("", CreatePatch(Command.Edit, new ConfigNode())); + }); - Assert.False(collection.HasMod("mod4")); - Assert.False(collection.HasMod("Mod4")); - Assert.False(collection.HasMod("MOD4")); + Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); + Assert.Equal("mod", ex.ParamName); } [Fact] - public void TestModPasses__Accessor() + public void TestAddForPatch__PatchNull() { - PatchList list = new PatchList(new[] { "mod1", "mod2" }); + ArgumentNullException ex = Assert.Throws(delegate + { + patchList.AddForPatch("mod1", null); + }); - PatchList.ModPass pass1 = list.modPasses["mod1"]; - Assert.NotNull(pass1); - Assert.Equal("mod1", pass1.name); - Assert.NotNull(pass1.beforePatches); - Assert.Equal(0, pass1.beforePatches.Capacity); - Assert.NotNull(pass1.forPatches); - Assert.Equal(0, pass1.forPatches.Capacity); - Assert.NotNull(pass1.afterPatches); - Assert.Equal(0, pass1.afterPatches.Capacity); + Assert.Equal("patch", ex.ParamName); + } - PatchList.ModPass pass2 = list.modPasses["mod2"]; - Assert.NotNull(pass2); - Assert.Equal("mod2", pass2.name); - Assert.NotNull(pass2.beforePatches); - Assert.Equal(0, pass2.beforePatches.Capacity); - Assert.NotNull(pass2.forPatches); - Assert.Equal(0, pass2.forPatches.Capacity); - Assert.NotNull(pass2.afterPatches); - Assert.Equal(0, pass2.afterPatches.Capacity); + [Fact] + public void TestAddForPatch__ModDoesNotExist() + { + KeyNotFoundException ex = Assert.Throws(delegate + { + patchList.AddForPatch("mod3", CreatePatch(Command.Edit, new ConfigNode())); + }); + + Assert.Equal("Mod 'mod3' not found", ex.Message); + } - Assert.Throws(delegate + [Fact] + public void TestAddAfterPatch__ModNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + patchList.AddAfterPatch(null, CreatePatch(Command.Edit, new ConfigNode())); + }); + + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestAddAfterPatch__ModBlank() + { + ArgumentException ex = Assert.Throws(delegate + { + patchList.AddAfterPatch("", CreatePatch(Command.Edit, new ConfigNode())); + }); + + Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestAddAfterPatch__PatchNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + patchList.AddAfterPatch("mod1", null); + }); + + Assert.Equal("patch", ex.ParamName); + } + + [Fact] + public void TestAddAddafterPatch__ModDoesNotExist() + { + KeyNotFoundException ex = Assert.Throws(delegate { - PatchList.ModPass mod3 = list.modPasses["mod3"]; + patchList.AddAfterPatch("mod3", CreatePatch(Command.Edit, new ConfigNode())); }); + + Assert.Equal("Mod 'mod3' not found", ex.Message); } [Fact] - public void TestModPasses__Enumeration() + public void TestAddFinalPatch__Null() { - PatchList list = new PatchList(new[] { "mod1", "mod2" }); + ArgumentNullException ex = Assert.Throws(delegate + { + patchList.AddFinalPatch(null); + }); - PatchList.ModPass[] passes = new PatchList.ModPass[] { list.modPasses["mod1"], list.modPasses["mod2"] }; + Assert.Equal("patch", ex.ParamName); + } - Assert.Equal(passes, list.modPasses); + private Patch CreatePatch(Command command, ConfigNode node) + { + return new Patch(new UrlDir.UrlConfig(file, node), command, node); } } } From fef311054bbd8b0298e7af79e09acd935a4409d3 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 11 Apr 2018 22:18:41 -0700 Subject: [PATCH 213/342] Allow null value in wildcard match Allows for instance :HAS[#someValue] instead of :HAS[#someValue[*]] --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index f713c0c3..5da80d98 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1799,7 +1799,7 @@ public static bool CheckConstraints(ConfigNode node, string constraints) public static bool WildcardMatchValues(ConfigNode node, string type, string value) { double val = 0; - bool compare = value.Length > 1 && (value[0] == '<' || value[0] == '>'); + bool compare = value != null && value.Length > 1 && (value[0] == '<' || value[0] == '>'); compare = compare && double.TryParse(value.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out val); string[] values = node.GetValues(type); From e134c5049ff49671538e6a64e9e494bf3f241a0b Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 11 Apr 2018 23:02:38 -0700 Subject: [PATCH 214/342] Extract node matcher Will eventually be useful in modifying nodes too, but not without some refactoring. For now it is only used on root patches. --- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/NodeMatcher.cs | 88 +++++ ModuleManager/Patch.cs | 3 + ModuleManager/PatchApplier.cs | 32 +- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/NodeMatcherTest.cs | 333 +++++++++++++++++++ ModuleManagerTests/PatchExtractorTest.cs | 53 +++ ModuleManagerTests/PatchTest.cs | 58 ++++ 8 files changed, 541 insertions(+), 28 deletions(-) create mode 100644 ModuleManager/NodeMatcher.cs create mode 100644 ModuleManagerTests/NodeMatcherTest.cs diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 4e0bd9da..c73adca4 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -57,6 +57,7 @@ + diff --git a/ModuleManager/NodeMatcher.cs b/ModuleManager/NodeMatcher.cs new file mode 100644 index 00000000..cef87865 --- /dev/null +++ b/ModuleManager/NodeMatcher.cs @@ -0,0 +1,88 @@ +using System; +using ModuleManager.Extensions; + +namespace ModuleManager +{ + public interface INodeMatcher + { + bool IsMatch(ConfigNode node); + } + + public class NodeMatcher : INodeMatcher + { + private static readonly char[] sep = { '[', ']' }; + + private string type; + private string[] namePatterns = null; + private string constraints = ""; + + public NodeMatcher(string nodeName) + { + if (nodeName == null) throw new ArgumentNullException(nameof(nodeName)); + if (nodeName == "") throw new ArgumentException("can't be empty", nameof(nodeName)); + if (!nodeName.IsBracketBalanced()) throw new FormatException("node name is not bracket balanced: " + nodeName); + string name = nodeName; + + int indexOfHas = name.IndexOf(":HAS[", StringComparison.InvariantCultureIgnoreCase); + + if (indexOfHas == 0) + { + throw new FormatException("node name cannot begin with :HAS : " + nodeName); + } + else if (indexOfHas > 0) + { + int closingBracketIndex = name.LastIndexOf(']', name.Length - 1, name.Length - indexOfHas - 1); + // Really shouldn't happen if we're bracket balanced but just in case + if (closingBracketIndex == -1) throw new FormatException("Malformed :HAS[] block detected: " + nodeName); + + constraints = name.Substring(indexOfHas + 5, closingBracketIndex - indexOfHas - 5); + name = name.Substring(0, indexOfHas); + } + + int bracketIndex = name.IndexOf('['); + if (bracketIndex == 0) + { + throw new FormatException("node name cannot begin with a bracket: " + nodeName); + } + else if (bracketIndex > 0) + { + int closingBracketIndex = name.LastIndexOf(']', name.Length - 1, name.Length - bracketIndex - 1); + // Really shouldn't happen if we're bracket balanced but just in case + if (closingBracketIndex == -1) throw new FormatException("Malformed brackets detected: " + nodeName); + string patterns = name.Substring(bracketIndex + 1, closingBracketIndex - bracketIndex - 1); + namePatterns = patterns.Split(',', '|'); + type = name.Substring(0, bracketIndex); + } + else + { + type = name; + namePatterns = null; + } + } + + public bool IsMatch(ConfigNode node) + { + if (node.name != type) return false; + + if (namePatterns != null) + { + string name = node.GetValue("name"); + if (name == null) return false; + + bool match = false; + foreach (string pattern in namePatterns) + { + if (MMPatchLoader.WildcardMatch(name, pattern)) + { + match = true; + break; + } + } + + if (!match) return false; + } + + return MMPatchLoader.CheckConstraints(node, constraints); + } + } +} diff --git a/ModuleManager/Patch.cs b/ModuleManager/Patch.cs index ba443205..1eb488f7 100644 --- a/ModuleManager/Patch.cs +++ b/ModuleManager/Patch.cs @@ -7,6 +7,7 @@ public class Patch public readonly UrlDir.UrlConfig urlConfig; public readonly Command command; public readonly ConfigNode node; + public readonly INodeMatcher nodeMatcher; public Patch(UrlDir.UrlConfig urlConfig, Command command, ConfigNode node) { @@ -16,6 +17,8 @@ public Patch(UrlDir.UrlConfig urlConfig, Command command, ConfigNode node) this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); this.command = command; this.node = node ?? throw new ArgumentNullException(nameof(node)); + + nodeMatcher = new NodeMatcher(node.name); } } } diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index c82ee866..68de463f 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -83,14 +83,14 @@ private void ApplyPatches(IPass pass) { foreach (UrlDir.UrlConfig url in file.configs) { - if (!IsMatch(url, type, patterns, condition)) continue; + if (!patch.nodeMatcher.IsMatch(url.config)) continue; if (loop) logger.Info($"Looping on {patch.urlConfig.SafeUrl()} to {url.SafeUrl()}"); do { progress.ApplyingUpdate(url, patch.urlConfig); url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), patch.node, context); - } while (loop && IsMatch(url, type, patterns, condition)); + } while (loop && patch.nodeMatcher.IsMatch(url.config)); if (loop) url.config.RemoveNodes("MM_PATCH_LOOP"); } @@ -102,7 +102,7 @@ private void ApplyPatches(IPass pass) for (int i = 0; i < count; i++) { UrlDir.UrlConfig url = file.configs[i]; - if (!IsMatch(url, type, patterns, condition)) continue; + if (!patch.nodeMatcher.IsMatch(url.config)) continue; ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), patch.node, context); if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) @@ -123,7 +123,7 @@ private void ApplyPatches(IPass pass) { UrlDir.UrlConfig url = file.configs[i]; - if (IsMatch(url, type, patterns, condition)) + if (patch.nodeMatcher.IsMatch(url.config)) { progress.ApplyingDelete(url, patch.urlConfig); file.configs.RemoveAt(i); @@ -156,29 +156,5 @@ private void ApplyPatches(IPass pass) } } } - - private static bool IsMatch(UrlDir.UrlConfig url, string type, string[] namePatterns, string constraints) - { - if (url.type != type) return false; - - if (namePatterns != null) - { - if (url.name == url.type) return false; - - bool match = false; - foreach (string pattern in namePatterns) - { - if (MMPatchLoader.WildcardMatch(url.name, pattern)) - { - match = true; - break; - } - } - - if (!match) return false; - } - - return MMPatchLoader.CheckConstraints(url.config, constraints); - } } } diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 23ad13a9..fe099e9c 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -60,6 +60,7 @@ + diff --git a/ModuleManagerTests/NodeMatcherTest.cs b/ModuleManagerTests/NodeMatcherTest.cs new file mode 100644 index 00000000..d1f1baf3 --- /dev/null +++ b/ModuleManagerTests/NodeMatcherTest.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; +using TestUtils; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class NodeMatcherTest + { + #region Constructor + + [Fact] + public void TestConstructor__Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new NodeMatcher(null); + }); + + Assert.Equal("nodeName", ex.ParamName); + } + + [Fact] + public void TestConstructor__Blank() + { + ArgumentException ex = Assert.Throws(delegate + { + new NodeMatcher(""); + }); + + Assert.Equal("nodeName", ex.ParamName); + Assert.Equal("can't be empty\r\nParameter name: nodeName", ex.Message); + } + + [Fact] + public void TestConstructor__NotBracketBalanced() + { + FormatException ex = Assert.Throws(delegate + { + new NodeMatcher("NODE[stuff"); + }); + + Assert.Equal("node name is not bracket balanced: NODE[stuff", ex.Message); + } + + [Fact] + public void TestConstructor__StartsWithHas() + { + FormatException ex = Assert.Throws(delegate + { + new NodeMatcher(":HAS[#blah]"); + }); + + Assert.Equal("node name cannot begin with :HAS : :HAS[#blah]", ex.Message); + } + + [Fact] + public void TestConstructor__StartsWithBracket() + { + FormatException ex = Assert.Throws(delegate + { + new NodeMatcher("[#blah]"); + }); + + Assert.Equal("node name cannot begin with a bracket: [#blah]", ex.Message); + } + + #endregion + + #region IsMatch + + [Fact] + public void TestIsMatch() + { + NodeMatcher matcher = new NodeMatcher("NODE"); + + Assert.True(matcher.IsMatch(new ConfigNode("NODE"))); + Assert.False(matcher.IsMatch(new ConfigNode("PART"))); + } + + [Fact] + public void TestIsMatch__Name() + { + NodeMatcher matcher = new NodeMatcher("NODE[blah]"); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("NODE"))); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "bleh" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("PART"))); + + Assert.False(matcher.IsMatch(new TestConfigNode("PART") + { + { "name", "blah" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("PART") + { + { "name", "bleh" }, + })); + } + + [Fact] + public void TestIsMatch__Name__Wildcard() + { + NodeMatcher matcher = new NodeMatcher("NODE[bl*h]"); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + })); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blablah" }, + })); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "bleh" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("NODE"))); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blue" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("PART"))); + + Assert.False(matcher.IsMatch(new TestConfigNode("PART") + { + { "name", "blah" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("PART") + { + { "name", "blue" }, + })); + } + + [Fact] + public void TestIsMatch__Name__Multiple() + { + NodeMatcher matcher = new NodeMatcher("NODE[blah|bleh|blih*]"); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + })); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "bleh" }, + })); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blih" }, + })); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blihblih" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("NODE"))); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "bloh" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("PART"))); + + Assert.False(matcher.IsMatch(new TestConfigNode("PART") + { + { "name", "blah" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("PART") + { + { "name", "bleh" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("PART") + { + { "name", "blih" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("PART") + { + { "name", "bloh" }, + })); + } + + [Fact] + public void TestIsMatch__Constraints() + { + NodeMatcher matcher = new NodeMatcher("NODE[blah]:HAS[@FOO[bar*],#something[else]]"); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + { "something", "else" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "bleh" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NADE") + { + { "name", "blah" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + } + + [Fact] + public void TestIsMatch__Constraints_Open() + { + NodeMatcher matcher = new NodeMatcher("NODE[blah]:HAS[@FOO,#something]"); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + { "something", "else" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "bleh" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NADE") + { + { "name", "blah" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + } + + #endregion + } +} diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 7871f5e8..3fce6b64 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -753,6 +753,12 @@ private void AssertPatchCorrect(Patch patch, UrlDir.UrlConfig originalUrl, Comma Assert.Same(originalNode.nodes[i], patch.node.nodes[i]); } + if (expectedNodeName == "NODE") + AssertNodeMatcher__Bare(patch.nodeMatcher); + else if (expectedNodeName == "NODE[foo]:HAS[#bar]") + AssertNodeMatcher__Name__Has(patch.nodeMatcher); + else + throw new NotImplementedException(); } private void AssertNoErrors() @@ -768,5 +774,52 @@ private void EnsureNeedsSatisfied() progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); } + + private void AssertNodeMatcher__Bare(INodeMatcher matcher) + { + Assert.True(matcher.IsMatch(new ConfigNode("NODE"))); + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "boo" }, + { "bar", "baz" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("NADE"))); + } + + private void AssertNodeMatcher__Name__Has(INodeMatcher matcher) + { + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "foo" }, + { "bar", "baz" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "foo" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "boo" }, + { "bar", "baz" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("NODE"))); + + Assert.False(matcher.IsMatch(new TestConfigNode("NADE") + { + { "name", "foo" }, + { "bar", "baz" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "boo" }, + { "bar", "baz" }, + })); + } } } diff --git a/ModuleManagerTests/PatchTest.cs b/ModuleManagerTests/PatchTest.cs index c8bda099..a20ebc1b 100644 --- a/ModuleManagerTests/PatchTest.cs +++ b/ModuleManagerTests/PatchTest.cs @@ -72,5 +72,63 @@ public void TestConstructor__NullNode() UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); Assert.Throws(() => new Patch(urlConfig, Command.Edit, null)); } + + [Fact] + public void TestNodeMatcher() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE[blah]:HAS[@FOO[bar*],#something[else]]")); + Patch patch = new Patch(urlConfig, Command.Edit, urlConfig.config); + INodeMatcher matcher = patch.nodeMatcher; + + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + { "something", "else" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "blah" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "bleh" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NADE") + { + { "name", "blah" }, + { "something", "else" }, + new TestConfigNode("FOO") + { + { "name", "barbar" }, + }, + })); + } } } From 53d238a7ccc585c66ef21536fa5dd62fbea70328 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 19 Apr 2018 00:07:26 -0700 Subject: [PATCH 215/342] Extract IPatch interface, split up root patches --- ModuleManager/ModuleManager.csproj | 6 +- ModuleManager/Pass.cs | 11 +- ModuleManager/Patch.cs | 24 - ModuleManager/PatchApplier.cs | 103 +-- ModuleManager/PatchExtractor.cs | 9 +- ModuleManager/PatchList.cs | 31 +- ModuleManager/Patches/CopyPatch.cs | 55 ++ ModuleManager/Patches/DeletePatch.cs | 48 ++ ModuleManager/Patches/EditPatch.cs | 54 ++ ModuleManager/Patches/IPatch.cs | 13 + ModuleManager/Patches/PatchCompiler.cs | 31 + ModuleManagerTests/ModuleManagerTests.csproj | 5 +- ModuleManagerTests/PassTest.cs | 22 +- ModuleManagerTests/PatchApplierTest.cs | 773 +----------------- ModuleManagerTests/PatchExtractorTest.cs | 113 ++- ModuleManagerTests/PatchListTest.cs | 61 +- ModuleManagerTests/PatchTest.cs | 134 --- ModuleManagerTests/Patches/CopyPatchTest.cs | 274 +++++++ ModuleManagerTests/Patches/DeletePatchTest.cs | 130 +++ ModuleManagerTests/Patches/EditPatchTest.cs | 235 ++++++ .../Patches/PatchCompilerTest.cs | 257 ++++++ 21 files changed, 1261 insertions(+), 1128 deletions(-) delete mode 100644 ModuleManager/Patch.cs create mode 100644 ModuleManager/Patches/CopyPatch.cs create mode 100644 ModuleManager/Patches/DeletePatch.cs create mode 100644 ModuleManager/Patches/EditPatch.cs create mode 100644 ModuleManager/Patches/IPatch.cs create mode 100644 ModuleManager/Patches/PatchCompiler.cs delete mode 100644 ModuleManagerTests/PatchTest.cs create mode 100644 ModuleManagerTests/Patches/CopyPatchTest.cs create mode 100644 ModuleManagerTests/Patches/DeletePatchTest.cs create mode 100644 ModuleManagerTests/Patches/EditPatchTest.cs create mode 100644 ModuleManagerTests/Patches/PatchCompilerTest.cs diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index c73adca4..0bb5a934 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -61,9 +61,13 @@ - + + + + + diff --git a/ModuleManager/Pass.cs b/ModuleManager/Pass.cs index 241c88b5..c7db8684 100644 --- a/ModuleManager/Pass.cs +++ b/ModuleManager/Pass.cs @@ -1,10 +1,11 @@ using System; using System.Collections; using System.Collections.Generic; +using ModuleManager.Patches; namespace ModuleManager { - public interface IPass : IEnumerable + public interface IPass : IEnumerable { string Name { get; } } @@ -12,7 +13,7 @@ public interface IPass : IEnumerable public class Pass : IPass { private readonly string name; - private readonly List patches = new List(0); + private readonly List patches = new List(0); public Pass(string name) { @@ -22,10 +23,10 @@ public Pass(string name) public string Name => name; - public void Add(Patch patch) => patches.Add(patch); + public void Add(IPatch patch) => patches.Add(patch); - public List.Enumerator GetEnumerator() => patches.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public List.Enumerator GetEnumerator() => patches.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/ModuleManager/Patch.cs b/ModuleManager/Patch.cs deleted file mode 100644 index 1eb488f7..00000000 --- a/ModuleManager/Patch.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace ModuleManager -{ - public class Patch - { - public readonly UrlDir.UrlConfig urlConfig; - public readonly Command command; - public readonly ConfigNode node; - public readonly INodeMatcher nodeMatcher; - - public Patch(UrlDir.UrlConfig urlConfig, Command command, ConfigNode node) - { - if (command != Command.Edit && command != Command.Copy && command != Command.Delete) - throw new ArgumentException($"Must be Edit, Copy, or Delete (got {command})", nameof(command)); - - this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); - this.command = command; - this.node = node ?? throw new ArgumentNullException(nameof(node)); - - nodeMatcher = new NodeMatcher(node.name); - } - } -} diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 68de463f..f98b7c49 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -2,8 +2,8 @@ using System.Linq; using ModuleManager.Logging; using ModuleManager.Extensions; +using ModuleManager.Patches; using ModuleManager.Progress; -using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager { @@ -11,8 +11,7 @@ public class PatchApplier { private readonly IBasicLogger logger; private readonly IPatchProgress progress; - - private readonly UrlDir databaseRoot; + private readonly IPatchList patchList; private readonly UrlDir.UrlFile[] allConfigFiles; @@ -22,7 +21,6 @@ public class PatchApplier public PatchApplier(IPatchList patchList, UrlDir databaseRoot, IPatchProgress progress, IBasicLogger logger) { this.patchList = patchList; - this.databaseRoot = databaseRoot; this.progress = progress; this.logger = logger; @@ -42,112 +40,23 @@ private void ApplyPatches(IPass pass) logger.Info(pass.Name + " pass"); Activity = "ModuleManager " + pass.Name; - foreach (Patch patch in pass) + foreach (IPatch patch in pass) { try { - string name = patch.node.name.RemoveWS(); - - if (patch.command == Command.Insert) - { - logger.Warning("Warning - Encountered insert node that should not exist at this stage: " + patch.urlConfig.SafeUrl()); - continue; - } - else if (patch.command != Command.Edit && patch.command != Command.Copy && patch.command != Command.Delete) - { - logger.Warning("Invalid command encountered on a patch: " + patch.urlConfig.SafeUrl()); - continue; - } - - string upperName = name.ToUpper(); - PatchContext context = new PatchContext(patch.urlConfig, databaseRoot, logger, progress); - char[] sep = { '[', ']' }; - string condition = ""; - - if (upperName.Contains(":HAS[")) - { - int start = upperName.IndexOf(":HAS["); - condition = name.Substring(start + 5, name.LastIndexOf(']') - start - 5); - name = name.Substring(0, start); - } - - string[] splits = name.Split(sep, 3); - string[] patterns = splits.Length > 1 ? splits[1].Split(',', '|') : null; - string type = splits[0]; - - bool loop = patch.node.HasNode("MM_PATCH_LOOP"); - foreach (UrlDir.UrlFile file in allConfigFiles) { - if (patch.command == Command.Edit) - { - foreach (UrlDir.UrlConfig url in file.configs) - { - if (!patch.nodeMatcher.IsMatch(url.config)) continue; - if (loop) logger.Info($"Looping on {patch.urlConfig.SafeUrl()} to {url.SafeUrl()}"); - - do - { - progress.ApplyingUpdate(url, patch.urlConfig); - url.config = MMPatchLoader.ModifyNode(new NodeStack(url.config), patch.node, context); - } while (loop && patch.nodeMatcher.IsMatch(url.config)); - - if (loop) url.config.RemoveNodes("MM_PATCH_LOOP"); - } - } - else if (patch.command == Command.Copy) - { - // Avoid checking the new configs we are creating - int count = file.configs.Count; - for (int i = 0; i < count; i++) - { - UrlDir.UrlConfig url = file.configs[i]; - if (!patch.nodeMatcher.IsMatch(url.config)) continue; - - ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), patch.node, context); - if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) - { - progress.Error(patch.urlConfig, $"Error - when applying copy {patch.urlConfig.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); - } - else - { - progress.ApplyingCopy(url, patch.urlConfig); - file.AddConfig(clone); - } - } - } - else if (patch.command == Command.Delete) - { - int i = 0; - while (i < file.configs.Count) - { - UrlDir.UrlConfig url = file.configs[i]; - - if (patch.nodeMatcher.IsMatch(url.config)) - { - progress.ApplyingDelete(url, patch.urlConfig); - file.configs.RemoveAt(i); - } - else - { - i++; - } - } - } - else - { - throw new NotImplementedException("This code should not be reachable"); - } + patch.Apply(file, progress, logger); } progress.PatchApplied(); } catch (Exception e) { - progress.Exception(patch.urlConfig, "Exception while processing node : " + patch.urlConfig.SafeUrl(), e); + progress.Exception(patch.UrlConfig, "Exception while processing node : " + patch.UrlConfig.SafeUrl(), e); try { - logger.Error("Processed node was\n" + patch.urlConfig.PrettyPrint()); + logger.Error("Processed node was\n" + patch.UrlConfig.PrettyPrint()); } catch (Exception ex2) { diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 5a583ad4..06de7182 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -2,6 +2,7 @@ using System.Text.RegularExpressions; using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.Patches; using ModuleManager.Progress; namespace ModuleManager @@ -122,7 +123,7 @@ public void ExtractPatch(UrlDir.UrlConfig urlConfig) urlConfig.parent.configs.Remove(urlConfig); Match theMatch = null; - Action addPatch = null; + Action addPatch = null; if (firstMatch.Success) { @@ -184,11 +185,7 @@ public void ExtractPatch(UrlDir.UrlConfig urlConfig) else newName = name.Remove(theMatch.Index, theMatch.Length); - ConfigNode node = new ConfigNode(newName) { id = urlConfig.config.id }; - node.ShallowCopyFrom(urlConfig.config); - Patch patch = new Patch(urlConfig, command, node); - - addPatch(patch); + addPatch(PatchCompiler.CompilePatch(urlConfig, command, newName)); progress.PatchAdded(); } catch(Exception e) diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index dff2fdb5..e79f9570 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -3,18 +3,19 @@ using System.Collections.Generic; using System.Linq; using ModuleManager.Collections; +using ModuleManager.Patches; namespace ModuleManager { public interface IPatchList : IEnumerable { bool HasMod(string mod); - void AddFirstPatch(Patch patch); - void AddLegacyPatch(Patch patch); - void AddBeforePatch(string mod, Patch patch); - void AddForPatch(string mod, Patch patch); - void AddAfterPatch(string mod, Patch patch); - void AddFinalPatch(Patch patch); + void AddFirstPatch(IPatch patch); + void AddLegacyPatch(IPatch patch); + void AddBeforePatch(string mod, IPatch patch); + void AddForPatch(string mod, IPatch patch); + void AddAfterPatch(string mod, IPatch patch); + void AddFinalPatch(IPatch patch); } public class PatchList : IPatchList @@ -37,9 +38,9 @@ public ModPass(string name) afterPass = new Pass($":AFTER[{this.name}]"); } - public void AddBeforePatch(Patch patch) => beforePass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); - public void AddForPatch(Patch patch) => forPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); - public void AddAfterPatch(Patch patch) => afterPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + public void AddBeforePatch(IPatch patch) => beforePass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + public void AddForPatch(IPatch patch) => forPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + public void AddAfterPatch(IPatch patch) => afterPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); } private class ModPassCollection : IEnumerable @@ -97,35 +98,35 @@ public bool HasMod(string mod) return modPasses.HasMod(mod); } - public void AddFirstPatch(Patch patch) + public void AddFirstPatch(IPatch patch) { firstPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); } - public void AddLegacyPatch(Patch patch) + public void AddLegacyPatch(IPatch patch) { legacyPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); } - public void AddBeforePatch(string mod, Patch patch) + public void AddBeforePatch(string mod, IPatch patch) { EnsureMod(mod); modPasses[mod].AddBeforePatch(patch ?? throw new ArgumentNullException(nameof(patch))); } - public void AddForPatch(string mod, Patch patch) + public void AddForPatch(string mod, IPatch patch) { EnsureMod(mod); modPasses[mod].AddForPatch(patch ?? throw new ArgumentNullException(nameof(patch))); } - public void AddAfterPatch(string mod, Patch patch) + public void AddAfterPatch(string mod, IPatch patch) { EnsureMod(mod); modPasses[mod].AddAfterPatch(patch ?? throw new ArgumentNullException(nameof(patch))); } - public void AddFinalPatch(Patch patch) + public void AddFinalPatch(IPatch patch) { finalPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); } diff --git a/ModuleManager/Patches/CopyPatch.cs b/ModuleManager/Patches/CopyPatch.cs new file mode 100644 index 00000000..e8fa295f --- /dev/null +++ b/ModuleManager/Patches/CopyPatch.cs @@ -0,0 +1,55 @@ +using System; +using NodeStack = ModuleManager.Collections.ImmutableStack; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using ModuleManager.Progress; + +namespace ModuleManager.Patches +{ + public class CopyPatch : IPatch + { + public UrlDir.UrlConfig UrlConfig { get; } + public INodeMatcher NodeMatcher { get; } + + public CopyPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher) + { + UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); + NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher)); + } + + public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger) + { + if (file == null) throw new ArgumentNullException(nameof(file)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + PatchContext context = new PatchContext(UrlConfig, file.root, logger, progress); + + // Avoid checking the new configs we are creating + int count = file.configs.Count; + for (int i = 0; i < count; i++) + { + UrlDir.UrlConfig url = file.configs[i]; + try + { + if (!NodeMatcher.IsMatch(url.config)) continue; + + ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), UrlConfig.config, context); + if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) + { + progress.Error(UrlConfig, $"Error - when applying copy {UrlConfig.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); + } + else + { + progress.ApplyingCopy(url, UrlConfig); + file.AddConfig(clone); + } + } + catch (Exception ex) + { + progress.Exception(UrlConfig, $"Exception while applying copy {UrlConfig.SafeUrl()} to {url.SafeUrl()}", ex); + } + } + } + } +} diff --git a/ModuleManager/Patches/DeletePatch.cs b/ModuleManager/Patches/DeletePatch.cs new file mode 100644 index 00000000..2fbcd879 --- /dev/null +++ b/ModuleManager/Patches/DeletePatch.cs @@ -0,0 +1,48 @@ +using System; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using ModuleManager.Progress; + +namespace ModuleManager.Patches +{ + public class DeletePatch : IPatch + { + public UrlDir.UrlConfig UrlConfig { get; } + public INodeMatcher NodeMatcher { get; } + + public DeletePatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher) + { + UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); + NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher)); + } + + public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger) + { + if (file == null) throw new ArgumentNullException(nameof(file)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + int i = 0; + while (i < file.configs.Count) + { + UrlDir.UrlConfig url = file.configs[i]; + try + { + if (NodeMatcher.IsMatch(url.config)) + { + progress.ApplyingDelete(url, UrlConfig); + file.configs.RemoveAt(i); + } + else + { + i++; + } + } + catch (Exception ex) + { + progress.Exception(UrlConfig, $"Exception while applying delete {UrlConfig.SafeUrl()} to {url.SafeUrl()}", ex); + } + } + } + } +} diff --git a/ModuleManager/Patches/EditPatch.cs b/ModuleManager/Patches/EditPatch.cs new file mode 100644 index 00000000..4dfcc22f --- /dev/null +++ b/ModuleManager/Patches/EditPatch.cs @@ -0,0 +1,54 @@ +using System; +using NodeStack = ModuleManager.Collections.ImmutableStack; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using ModuleManager.Progress; + +namespace ModuleManager.Patches +{ + public class EditPatch : IPatch + { + private readonly bool loop; + + public UrlDir.UrlConfig UrlConfig { get; } + public INodeMatcher NodeMatcher { get; } + + public EditPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher) + { + UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); + NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher)); + + loop = urlConfig.config.HasNode("MM_PATCH_LOOP"); + } + + public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger) + { + if (file == null) throw new ArgumentNullException(nameof(file)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + PatchContext context = new PatchContext(UrlConfig, file.root, logger, progress); + for (int i = 0; i < file.configs.Count; i++) + { + UrlDir.UrlConfig urlConfig = file.configs[i]; + try + { + if (!NodeMatcher.IsMatch(urlConfig.config)) continue; + if (loop) logger.Info($"Looping on {UrlConfig.SafeUrl()} to {urlConfig.SafeUrl()}"); + + do + { + progress.ApplyingUpdate(urlConfig, UrlConfig); + file.configs[i] = urlConfig = new UrlDir.UrlConfig(file, MMPatchLoader.ModifyNode(new NodeStack(urlConfig.config), UrlConfig.config, context)); + } while (loop && NodeMatcher.IsMatch(urlConfig.config)); + + if (loop) file.configs[i].config.RemoveNodes("MM_PATCH_LOOP"); + } + catch (Exception ex) + { + progress.Exception(UrlConfig, $"Exception while applying update {UrlConfig.SafeUrl()} to {urlConfig.SafeUrl()}", ex); + } + } + } + } +} diff --git a/ModuleManager/Patches/IPatch.cs b/ModuleManager/Patches/IPatch.cs new file mode 100644 index 00000000..57ae2cfa --- /dev/null +++ b/ModuleManager/Patches/IPatch.cs @@ -0,0 +1,13 @@ +using System; +using ModuleManager.Logging; +using ModuleManager.Progress; + +namespace ModuleManager.Patches +{ + public interface IPatch + { + UrlDir.UrlConfig UrlConfig { get; } + INodeMatcher NodeMatcher { get; } + void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger); + } +} diff --git a/ModuleManager/Patches/PatchCompiler.cs b/ModuleManager/Patches/PatchCompiler.cs new file mode 100644 index 00000000..3e71a3ce --- /dev/null +++ b/ModuleManager/Patches/PatchCompiler.cs @@ -0,0 +1,31 @@ +using System; + +namespace ModuleManager.Patches +{ + public static class PatchCompiler + { + public static IPatch CompilePatch(UrlDir.UrlConfig urlConfig, Command command, string name) + { + if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig)); + if (name == null) throw new ArgumentNullException(nameof(name)); + if (name == string.Empty) throw new ArgumentException("can't be empty", nameof(name)); + + INodeMatcher nodeMatcher = new NodeMatcher(name); + + switch (command) + { + case Command.Edit: + return new EditPatch(urlConfig, nodeMatcher); + + case Command.Copy: + return new CopyPatch(urlConfig, nodeMatcher); + + case Command.Delete: + return new DeletePatch(urlConfig, nodeMatcher); + + default: + throw new ArgumentException("invalid command for a root node: " + command, nameof(command)); + } + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index fe099e9c..87d7ec27 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -64,6 +64,10 @@ + + + + @@ -73,7 +77,6 @@ - diff --git a/ModuleManagerTests/PassTest.cs b/ModuleManagerTests/PassTest.cs index 85300ef5..9e712e49 100644 --- a/ModuleManagerTests/PassTest.cs +++ b/ModuleManagerTests/PassTest.cs @@ -1,7 +1,10 @@ using System; +using System.Linq; using Xunit; +using NSubstitute; using TestUtils; using ModuleManager; +using ModuleManager.Patches; namespace ModuleManagerTests { @@ -48,11 +51,11 @@ public void TestName() [Fact] public void Test__Add__Enumerator() { - Patch[] patches = + IPatch[] patches = { - CreatePatch(Command.Edit, new ConfigNode()), - CreatePatch(Command.Copy, new ConfigNode()), - CreatePatch(Command.Delete, new ConfigNode()), + Substitute.For(), + Substitute.For(), + Substitute.For(), }; Pass pass = new Pass("blah") @@ -62,12 +65,13 @@ public void Test__Add__Enumerator() patches[2], }; - Assert.Equal(patches, pass); - } + IPatch[] passPatches = pass.ToArray(); + Assert.Equal(patches.Length, passPatches.Length); - private Patch CreatePatch(Command command, ConfigNode node) - { - return new Patch(new UrlDir.UrlConfig(file, node), command, node); + for (int i = 0; i < patches.Length; i++) + { + Assert.Same(patches[i], passPatches[i]); + } } } } diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index 5929ddf5..8d07bd3c 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -1,13 +1,12 @@ using System; -using System.Linq; using Xunit; using NSubstitute; using UnityEngine; using TestUtils; using ModuleManager; using ModuleManager.Collections; -using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.Patches; using ModuleManager.Progress; namespace ModuleManagerTests @@ -43,739 +42,30 @@ public PatchApplierTest() } [Fact] - public void TestApplyPatches__Edit() + public void TestApplyPatches() { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "abc" }, - { "foo", "bar" }, - }, file); - - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "def" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") - { - { "name", "ghi" }, - { "jkl", "mno" }, - }, file); - - Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART") - { - { "@foo", "baz" }, - { "pqr", "stw" }, - }); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingUpdate(config2, patch1.urlConfig); - progress.Received(1).PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(3, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "abc" }, - { "foo", "baz" }, - { "pqr", "stw" }, - }, allConfigs[0].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "def" }, - { "pqr", "stw" }, - }, allConfigs[1].config); - - AssertNodesEqual(new TestConfigNode("PORT") - { - { "name", "ghi" }, - { "jkl", "mno" }, - }, allConfigs[2].config); - } - - [Fact] - public void TestApplyPatches__Copy() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, file); - - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "002" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") - { - { "name", "003" }, - { "bbb", "004" }, - }, file); - - Patch patch1 = CreatePatch(Command.Copy, new TestConfigNode("+PART") - { - { "@name ^", ":^00:01:" }, - { "@aaa", "011" }, - { "ccc", "005" }, - }); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().ApplyingCopy(config1, patch1.urlConfig); - progress.Received().ApplyingCopy(config2, patch1.urlConfig); - progress.Received(1).PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(5, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, allConfigs[0].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "002" }, - }, allConfigs[1].config); - - AssertNodesEqual(new TestConfigNode("PORT") - { - { "name", "003" }, - { "bbb", "004" }, - }, allConfigs[2].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "010" }, - { "aaa", "011" }, - { "ccc", "005" }, - }, allConfigs[3].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "012" }, - { "ccc", "005" }, - }, allConfigs[4].config); - } - - [Fact] - public void TestApplyPatches__Delete() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "abc" }, - { "foo", "bar" }, - }, file); - - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "def" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") - { - { "name", "ghi" }, - { "jkl", "mno" }, - }, file); - - Patch patch1 = CreatePatch(Command.Delete, new TestConfigNode("-PART")); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().ApplyingDelete(config1, patch1.urlConfig); - progress.Received().ApplyingDelete(config2, patch1.urlConfig); - progress.Received(1).PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(1, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PORT") - { - { "name", "ghi" }, - { "jkl", "mno" }, - }, allConfigs[0].config); - } - - [Fact] - public void TestApplyPatches__Name() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, file); - - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "002" }, - { "bbb", "003" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "004" }, - { "ccc", "005" }, - }, file); - - UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") - { - { "name", "006" }, - { "ddd", "007" }, - }, file); - - Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000]") - { - { "@aaa", "011" }, - { "eee", "012" }, - }); - - Patch patch2 = CreatePatch(Command.Copy, new TestConfigNode("+PART[002]") - { - { "@name", "022" }, - { "@bbb", "013" }, - { "fff", "014" }, - }); - - Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART[004]")); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1, patch2, patch3)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingCopy(config2, patch2.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingDelete(config3, patch3.urlConfig); - progress.Received().PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(4, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "011" }, - { "eee", "012" }, - }, allConfigs[0].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "002" }, - { "bbb", "003" }, - }, allConfigs[1].config); - - AssertNodesEqual(new TestConfigNode("PORT") - { - { "name", "006" }, - { "ddd", "007" }, - }, allConfigs[2].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "022" }, - { "bbb", "013" }, - { "fff", "014" }, - }, allConfigs[3].config); - } - - [Fact] - public void TestApplyPatches__Name__Wildcard() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, file); - - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "002" }, - { "bbb", "003" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "004" }, - { "ccc", "005" }, - }, file); - - UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") - { - { "name", "006" }, - { "ddd", "007" }, - }, file); - - Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART[0*0]") - { - { "@aaa", "011" }, - { "eee", "012" }, - }); - - Patch patch2 = CreatePatch(Command.Copy, new TestConfigNode("+PART[0*2]") - { - { "@name", "022" }, - { "@bbb", "013" }, - { "fff", "014" }, - }); - - Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART[0*4]")); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1, patch2, patch3)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingCopy(config2, patch2.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingDelete(config3, patch3.urlConfig); - progress.Received().PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(4, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "011" }, - { "eee", "012" }, - }, allConfigs[0].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "002" }, - { "bbb", "003" }, - }, allConfigs[1].config); - - AssertNodesEqual(new TestConfigNode("PORT") - { - { "name", "006" }, - { "ddd", "007" }, - }, allConfigs[2].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "022" }, - { "bbb", "013" }, - { "fff", "014" }, - }, allConfigs[3].config); - } - - [Fact] - public void TestApplyPatches__Name__Or() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, file); - - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "002" }, - { "bbb", "003" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "004" }, - { "ccc", "005" }, - }, file); - - Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "@aaa", "011" }, - { "ddd", "006" }, - }); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingUpdate(config2, patch1.urlConfig); - progress.Received().PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(3, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "011" }, - { "ddd", "006" }, - }, allConfigs[0].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "002" }, - { "bbb", "003" }, - { "ddd", "006" }, - }, allConfigs[1].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "004" }, - { "ccc", "005" }, - }, allConfigs[2].config); - } - - [Fact] - public void TestApplyPatches__Order() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, file); - - Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "bbb", "002" }, - }); - - Patch patch2 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "ccc", "003" }, - }); - - Patch patch3 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "ddd", "004" }, - }); - - Patch patch4 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "eee", "005" }, - }); - - Patch patch5 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "fff", "006" }, - }); - - Patch patch6 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "ggg", "007" }, - }); - - Patch patch7 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "hhh", "008" }, - }); - - Patch patch8 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "iii", "009" }, - }); - - Patch patch9 = CreatePatch(Command.Edit, new TestConfigNode("@PART[000|0*2]") - { - { "jjj", "010" }, - }); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1, patch2, patch3)); - pass2.GetEnumerator().Returns(new ArrayEnumerator(patch4, patch5, patch6)); - pass3.GetEnumerator().Returns(new ArrayEnumerator(patch7, patch8, patch9)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch2.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch3.urlConfig); - progress.Received().PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - progress.Received().ApplyingUpdate(config1, patch4.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch5.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch6.urlConfig); - progress.Received().PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - progress.Received().ApplyingUpdate(config1, patch7.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch8.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingUpdate(config1, patch9.urlConfig); - progress.Received().PatchApplied(); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(1, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - { "bbb", "002" }, - { "ccc", "003" }, - { "ddd", "004" }, - { "eee", "005" }, - { "fff", "006" }, - { "ggg", "007" }, - { "hhh", "008" }, - { "iii", "009" }, - { "jjj", "010" }, - }, allConfigs[0].config); - } - - [Fact] - public void TestApplyPatches__Constraints() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("PART") { { "name", "000" }, { "aaa", "001" }, }, file); - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "002" }, - { "bbb", "003" }, - }, file); - - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "004" }, - { "ccc", "005" }, - }, file); - - UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new TestConfigNode("PORT") - { - { "name", "006" }, - { "ddd", "007" }, - }, file); - - Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART:HAS[#aaa[001]]") - { - { "@aaa", "011" }, - { "eee", "012" }, - }); - - Patch patch2 = CreatePatch(Command.Copy, new TestConfigNode("+PART:HAS[#bbb[003]]") - { - { "@name", "012" }, - { "@bbb", "013" }, - { "fff", "014" }, - }); - - Patch patch3 = CreatePatch(Command.Delete, new TestConfigNode("!PART:HAS[#ccc[005]]")); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1, patch2, patch3)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingCopy(config2, patch2.urlConfig); - progress.Received().PatchApplied(); - progress.Received().ApplyingDelete(config3, patch3.urlConfig); - progress.Received().PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(4, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "011" }, - { "eee", "012" }, - }, allConfigs[0].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "002" }, - { "bbb", "003" }, - }, allConfigs[1].config); - - AssertNodesEqual(new TestConfigNode("PORT") - { - { "name", "006" }, - { "ddd", "007" }, - }, allConfigs[2].config); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "012" }, - { "bbb", "013" }, - { "fff", "014" }, - }, allConfigs[3].config); - } - - [Fact] - public void TestApplyPatches__Loop() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "1" }, - }, file); - - Patch patch1 = CreatePatch(Command.Edit, new TestConfigNode("@PART:HAS[~aaa[>10]]") - { - { "@aaa *", "2" }, - { "bbb", "002" }, - new ConfigNode("MM_PATCH_LOOP"), - }); - - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); - - patchApplier.ApplyPatches(); - - EnsureNoErrors(); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - logger.Received().Log(LogType.Log, "Looping on abc/def/@PART:HAS[~aaa[>10]] to abc/def/PART"); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().ApplyingUpdate(config1, patch1.urlConfig); - progress.Received().PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(1, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "16" }, - { "bbb", "002" }, - { "bbb", "002" }, - { "bbb", "002" }, - { "bbb", "002" }, - }, allConfigs[0].config); - } + UrlDir.UrlConfig[] patchUrlConfigs = new UrlDir.UrlConfig[9]; + IPatch[] patches = new IPatch[9]; - [Fact] - public void TestApplyPatches__Copy__NameNotChanged() - { - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("PART") + for (int i = 0; i < 9; i++) { - { "name", "000" }, - { "aaa", "001" }, - }, file); - - Patch patch1 = CreatePatch(Command.Copy, new TestConfigNode("+PART") - { - { "@aaa", "011" }, - { "bbb", "012" }, - }); + patchUrlConfigs[i] = UrlBuilder.CreateConfig(new ConfigNode(), file); + patches[i] = Substitute.For(); + patches[i].UrlConfig.Returns(patchUrlConfigs[i]); + } - pass1.GetEnumerator().Returns(new ArrayEnumerator(patch1)); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patches[0], patches[1], patches[2])); + pass2.GetEnumerator().Returns(new ArrayEnumerator(patches[3], patches[4], patches[5])); + pass3.GetEnumerator().Returns(new ArrayEnumerator(patches[6], patches[7], patches[8])); patchApplier.ApplyPatches(); - - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - logger.DidNotReceiveWithAnyArgs().Exception(null, null); - - progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); - progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); - progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); - - Received.InOrder(delegate - { - logger.Received().Log(LogType.Log, ":PASS1 pass"); - progress.Received().Error(patch1.urlConfig, "Error - when applying copy abc/def/+PART to abc/def/PART - the copy needs to have a different name than the parent (use @name = xxx)"); - progress.Received().PatchApplied(); - logger.Received().Log(LogType.Log, ":PASS2 pass"); - logger.Received().Log(LogType.Log, ":PASS3 pass"); - }); - - UrlDir.UrlConfig[] allConfigs = databaseRoot.AllConfigs.ToArray(); - Assert.Equal(1, allConfigs.Length); - - AssertNodesEqual(new TestConfigNode("PART") - { - { "name", "000" }, - { "aaa", "001" }, - }, allConfigs[0].config); - } - - private void EnsureNoErrors() - { progress.DidNotReceiveWithAnyArgs().Error(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); @@ -783,22 +73,31 @@ private void EnsureNoErrors() logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); logger.DidNotReceive().Log(LogType.Error, Arg.Any()); logger.DidNotReceiveWithAnyArgs().Exception(null, null); - } - - private void AssertNodesEqual(ConfigNode expected, ConfigNode actual) - { - Assert.Equal(expected.ToString(), actual.ToString()); - } - private Patch CreatePatch(Command command, ConfigNode node) - { - ConfigNode newNode = node; - if (command != Command.Insert) + Received.InOrder(delegate { - newNode = new ConfigNode(node.name.Substring(1)); - newNode.ShallowCopyFrom(node); - } - return new Patch(new UrlDir.UrlConfig(file, node), command, newNode); + logger.Log(LogType.Log, ":PASS1 pass"); + patches[0].Apply(file, progress, logger); + progress.PatchApplied(); + patches[1].Apply(file, progress, logger); + progress.PatchApplied(); + patches[2].Apply(file, progress, logger); + progress.PatchApplied(); + logger.Log(LogType.Log, ":PASS2 pass"); + patches[3].Apply(file, progress, logger); + progress.PatchApplied(); + patches[4].Apply(file, progress, logger); + progress.PatchApplied(); + patches[5].Apply(file, progress, logger); + progress.PatchApplied(); + logger.Log(LogType.Log, ":PASS3 pass"); + patches[6].Apply(file, progress, logger); + progress.PatchApplied(); + patches[7].Apply(file, progress, logger); + progress.PatchApplied(); + patches[8].Apply(file, progress, logger); + progress.PatchApplied(); + }); } } } diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 3fce6b64..0adcbe90 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -5,6 +5,7 @@ using TestUtils; using ModuleManager; using ModuleManager.Logging; +using ModuleManager.Patches; using ModuleManager.Progress; namespace ModuleManagerTests @@ -94,8 +95,8 @@ public void TestExtractPatch__First() UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:First"); UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:first"); - List patches = new List(); - patchList.AddFirstPatch(Arg.Do(patch => patches.Add(patch))); + List patches = new List(); + patchList.AddFirstPatch(Arg.Do(patch => patches.Add(patch))); extractor.ExtractPatch(patchConfig1); extractor.ExtractPatch(patchConfig2); @@ -107,10 +108,10 @@ public void TestExtractPatch__First() Assert.Empty(root.AllConfigs); Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); - AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); - AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); + AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); + AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); Received.InOrder(delegate { @@ -139,8 +140,8 @@ public void TestExtractPatch__Legacy() UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]"); UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE"); - List patches = new List(); - patchList.AddLegacyPatch(Arg.Do(patch => patches.Add(patch))); + List patches = new List(); + patchList.AddLegacyPatch(Arg.Do(patch => patches.Add(patch))); extractor.ExtractPatch(patchConfig1); extractor.ExtractPatch(patchConfig2); @@ -150,8 +151,8 @@ public void TestExtractPatch__Legacy() Assert.Empty(root.AllConfigs); Assert.Equal(2, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); + AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); + AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); Received.InOrder(delegate { @@ -180,8 +181,8 @@ public void TestExtractPatch__BeforeMod() UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:Before[mod1]"); UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:before[MOD1]"); - List patches = new List(); - patchList.AddBeforePatch("mod1", Arg.Do(patch => patches.Add(patch))); + List patches = new List(); + patchList.AddBeforePatch("mod1", Arg.Do(patch => patches.Add(patch))); extractor.ExtractPatch(patchConfig1); extractor.ExtractPatch(patchConfig2); @@ -193,10 +194,10 @@ public void TestExtractPatch__BeforeMod() Assert.Empty(root.AllConfigs); Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); - AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); - AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); + AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); + AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); Received.InOrder(delegate { @@ -267,8 +268,8 @@ public void TestExtractPatch__ForMod() UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:For[mod1]"); UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:for[MOD1]"); - List patches = new List(); - patchList.AddForPatch("mod1", Arg.Do(patch => patches.Add(patch))); + List patches = new List(); + patchList.AddForPatch("mod1", Arg.Do(patch => patches.Add(patch))); extractor.ExtractPatch(patchConfig1); extractor.ExtractPatch(patchConfig2); @@ -280,10 +281,10 @@ public void TestExtractPatch__ForMod() Assert.Empty(root.AllConfigs); Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); - AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); - AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); + AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); + AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); Received.InOrder(delegate @@ -355,8 +356,8 @@ public void TestExtractPatch__AfterMod() UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:After[mod1]"); UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:after[MOD1]"); - List patches = new List(); - patchList.AddAfterPatch("mod1", Arg.Do(patch => patches.Add(patch))); + List patches = new List(); + patchList.AddAfterPatch("mod1", Arg.Do(patch => patches.Add(patch))); extractor.ExtractPatch(patchConfig1); extractor.ExtractPatch(patchConfig2); @@ -366,10 +367,10 @@ public void TestExtractPatch__AfterMod() AssertNoErrors(); Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); - AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); - AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); + AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); + AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); Assert.Empty(root.AllConfigs); @@ -440,8 +441,8 @@ public void TestExtractPatch__Final() UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:Final"); UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:final"); - List patches = new List(); - patchList.AddFinalPatch(Arg.Do(patch => patches.Add(patch))); + List patches = new List(); + patchList.AddFinalPatch(Arg.Do(patch => patches.Add(patch))); extractor.ExtractPatch(patchConfig1); extractor.ExtractPatch(patchConfig2); @@ -453,10 +454,10 @@ public void TestExtractPatch__Final() Assert.Empty(root.AllConfigs); Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, Command.Edit, "NODE[foo]:HAS[#bar]"); - AssertPatchCorrect(patches[1], patchConfig2, Command.Edit, "NODE"); - AssertPatchCorrect(patches[2], patchConfig3, Command.Edit, "NODE"); - AssertPatchCorrect(patches[3], patchConfig4, Command.Edit, "NODE"); + AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); + AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); Received.InOrder(delegate { @@ -660,8 +661,8 @@ public void TestExtractPatch__Command() UrlDir.UrlConfig config09 = CreateConfig("#NODE:FOR[mod1]"); UrlDir.UrlConfig config10 = CreateConfig("*NODE:FOR[mod1]"); - List patches = new List(); - patchList.AddForPatch("mod1", Arg.Do(patch => patches.Add(patch))); + List patches = new List(); + patchList.AddForPatch("mod1", Arg.Do(patch => patches.Add(patch))); extractor.ExtractPatch(config01); extractor.ExtractPatch(config02); @@ -680,11 +681,11 @@ public void TestExtractPatch__Command() Assert.Empty(root.AllConfigs); Assert.Equal(5, patches.Count); - AssertPatchCorrect(patches[0], config01, Command.Edit, "NODE"); - AssertPatchCorrect(patches[1], config02, Command.Copy, "NODE"); - AssertPatchCorrect(patches[2], config03, Command.Copy, "NODE"); - AssertPatchCorrect(patches[3], config04, Command.Delete, "NODE"); - AssertPatchCorrect(patches[4], config05, Command.Delete, "NODE"); + AssertPatchCorrect(patches[0], config01, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[1], config02, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[2], config03, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[3], config04, AssertNodeMatcher__Bare); + AssertPatchCorrect(patches[4], config05, AssertNodeMatcher__Bare); progress.Received().PatchAdded(); Received.InOrder(delegate @@ -731,34 +732,12 @@ private UrlDir.UrlConfig CreateConfig(string name) return UrlBuilder.CreateConfig(node, file); } - private void AssertPatchCorrect(Patch patch, UrlDir.UrlConfig originalUrl, Command expectedCommand, string expectedNodeName) + private void AssertPatchCorrect(IPatch patch, UrlDir.UrlConfig originalUrl, Action assertNodeMatcher) where T : IPatch { - Assert.Same(originalUrl, patch.urlConfig); - Assert.Equal(expectedCommand, patch.command); - Assert.Equal(expectedNodeName, patch.node.name); + Assert.IsType(patch); + Assert.Same(originalUrl, patch.UrlConfig); - ConfigNode originalNode = originalUrl.config; - - Assert.Equal(originalNode.id, patch.node.id); - Assert.Equal(originalNode.values.Count, patch.node.values.Count); - Assert.Equal(originalNode.nodes.Count, patch.node.nodes.Count); - - for (int i = 0; i < originalNode.values.Count; i++) - { - Assert.Same(originalNode.values[i], patch.node.values[i]); - } - - for (int i = 0; i < originalNode.nodes.Count; i++) - { - Assert.Same(originalNode.nodes[i], patch.node.nodes[i]); - } - - if (expectedNodeName == "NODE") - AssertNodeMatcher__Bare(patch.nodeMatcher); - else if (expectedNodeName == "NODE[foo]:HAS[#bar]") - AssertNodeMatcher__Name__Has(patch.nodeMatcher); - else - throw new NotImplementedException(); + assertNodeMatcher(patch.NodeMatcher); } private void AssertNoErrors() diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index ce252c13..474ef64d 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Linq; using Xunit; +using NSubstitute; using TestUtils; using ModuleManager; +using ModuleManager.Patches; namespace ModuleManagerTests { @@ -24,24 +26,24 @@ public PatchListTest() [Fact] public void Test__Lifecycle() { - Patch patch01 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch02 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch03 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch04 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch05 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch06 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch07 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch08 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch09 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch10 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch11 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch12 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch13 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch14 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch15 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch16 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch17 = CreatePatch(Command.Edit, new ConfigNode("blah")); - Patch patch18 = CreatePatch(Command.Edit, new ConfigNode("blah")); + IPatch patch01 = Substitute.For(); + IPatch patch02 = Substitute.For(); + IPatch patch03 = Substitute.For(); + IPatch patch04 = Substitute.For(); + IPatch patch05 = Substitute.For(); + IPatch patch06 = Substitute.For(); + IPatch patch07 = Substitute.For(); + IPatch patch08 = Substitute.For(); + IPatch patch09 = Substitute.For(); + IPatch patch10 = Substitute.For(); + IPatch patch11 = Substitute.For(); + IPatch patch12 = Substitute.For(); + IPatch patch13 = Substitute.For(); + IPatch patch14 = Substitute.For(); + IPatch patch15 = Substitute.For(); + IPatch patch16 = Substitute.For(); + IPatch patch17 = Substitute.For(); + IPatch patch18 = Substitute.For(); patchList.AddFirstPatch(patch01); patchList.AddFirstPatch(patch02); @@ -155,7 +157,7 @@ public void TestAddBeforePatch__ModNull() { ArgumentNullException ex = Assert.Throws(delegate { - patchList.AddBeforePatch(null, CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddBeforePatch(null, Substitute.For()); }); Assert.Equal("mod", ex.ParamName); @@ -166,7 +168,7 @@ public void TestAddBeforePatch__ModBlank() { ArgumentException ex = Assert.Throws(delegate { - patchList.AddBeforePatch("", CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddBeforePatch("", Substitute.For()); }); Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); @@ -189,7 +191,7 @@ public void TestAddBeforePatch__ModDoesNotExist() { KeyNotFoundException ex = Assert.Throws(delegate { - patchList.AddBeforePatch("mod3", CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddBeforePatch("mod3", Substitute.For()); }); Assert.Equal("Mod 'mod3' not found", ex.Message); @@ -200,7 +202,7 @@ public void TestAddForPatch__ModNull() { ArgumentNullException ex = Assert.Throws(delegate { - patchList.AddForPatch(null, CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddForPatch(null, Substitute.For()); }); Assert.Equal("mod", ex.ParamName); @@ -211,7 +213,7 @@ public void TestAddForPatch__ModBlank() { ArgumentException ex = Assert.Throws(delegate { - patchList.AddForPatch("", CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddForPatch("", Substitute.For()); }); Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); @@ -234,7 +236,7 @@ public void TestAddForPatch__ModDoesNotExist() { KeyNotFoundException ex = Assert.Throws(delegate { - patchList.AddForPatch("mod3", CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddForPatch("mod3", Substitute.For()); }); Assert.Equal("Mod 'mod3' not found", ex.Message); @@ -245,7 +247,7 @@ public void TestAddAfterPatch__ModNull() { ArgumentNullException ex = Assert.Throws(delegate { - patchList.AddAfterPatch(null, CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddAfterPatch(null, Substitute.For()); }); Assert.Equal("mod", ex.ParamName); @@ -256,7 +258,7 @@ public void TestAddAfterPatch__ModBlank() { ArgumentException ex = Assert.Throws(delegate { - patchList.AddAfterPatch("", CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddAfterPatch("", Substitute.For()); }); Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); @@ -279,7 +281,7 @@ public void TestAddAddafterPatch__ModDoesNotExist() { KeyNotFoundException ex = Assert.Throws(delegate { - patchList.AddAfterPatch("mod3", CreatePatch(Command.Edit, new ConfigNode())); + patchList.AddAfterPatch("mod3", Substitute.For()); }); Assert.Equal("Mod 'mod3' not found", ex.Message); @@ -295,10 +297,5 @@ public void TestAddFinalPatch__Null() Assert.Equal("patch", ex.ParamName); } - - private Patch CreatePatch(Command command, ConfigNode node) - { - return new Patch(new UrlDir.UrlConfig(file, node), command, node); - } } } diff --git a/ModuleManagerTests/PatchTest.cs b/ModuleManagerTests/PatchTest.cs deleted file mode 100644 index a20ebc1b..00000000 --- a/ModuleManagerTests/PatchTest.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using Xunit; -using NSubstitute; -using TestUtils; -using ModuleManager; - -namespace ModuleManagerTests -{ - public class PatchTest - { - [Fact] - public void TestConstructor() - { - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); - ConfigNode node = new ConfigNode("NADE"); - Patch patch = new Patch(urlConfig, Command.Edit, node); - - Assert.Same(urlConfig, patch.urlConfig); - Assert.Equal(Command.Edit, patch.command); - Assert.Same(node, patch.node); - } - - [Fact] - public void TestConstructor__ValidCommands() - { - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); - ConfigNode node = new ConfigNode("NADE"); - - Patch patch1 = new Patch(urlConfig, Command.Edit, node); - Assert.Same(urlConfig, patch1.urlConfig); - Assert.Equal(Command.Edit, patch1.command); - Assert.Same(node, patch1.node); - - Patch patch2 = new Patch(urlConfig, Command.Copy, node); - Assert.Same(urlConfig, patch2.urlConfig); - Assert.Equal(Command.Copy, patch2.command); - Assert.Same(node, patch2.node); - - Patch patch3 = new Patch(urlConfig, Command.Delete, node); - Assert.Same(urlConfig, patch3.urlConfig); - Assert.Equal(Command.Delete, patch3.command); - Assert.Same(node, patch3.node); - - ArgumentException ex1 = Assert.Throws(() => new Patch(urlConfig, Command.Create, node)); - Assert.Equal("Must be Edit, Copy, or Delete (got Create)\r\nParameter name: command", ex1.Message); - - ArgumentException ex2 = Assert.Throws(() => new Patch(urlConfig, Command.Insert, node)); - Assert.Equal("Must be Edit, Copy, or Delete (got Insert)\r\nParameter name: command", ex2.Message); - - ArgumentException ex3 = Assert.Throws(() => new Patch(urlConfig, Command.Paste, node)); - Assert.Equal("Must be Edit, Copy, or Delete (got Paste)\r\nParameter name: command", ex3.Message); - - ArgumentException ex4 = Assert.Throws(() => new Patch(urlConfig, Command.Rename, node)); - Assert.Equal("Must be Edit, Copy, or Delete (got Rename)\r\nParameter name: command", ex4.Message); - - ArgumentException ex5 = Assert.Throws(() => new Patch(urlConfig, Command.Replace, node)); - Assert.Equal("Must be Edit, Copy, or Delete (got Replace)\r\nParameter name: command", ex5.Message); - - ArgumentException ex6 = Assert.Throws(() => new Patch(urlConfig, Command.Special, node)); - Assert.Equal("Must be Edit, Copy, or Delete (got Special)\r\nParameter name: command", ex6.Message); - } - - [Fact] - public void TestConstructor__NullUrlConfig() - { - Assert.Throws(() => new Patch(null, Command.Edit, new ConfigNode("BLAH"))); - } - - [Fact] - public void TestConstructor__NullNode() - { - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); - Assert.Throws(() => new Patch(urlConfig, Command.Edit, null)); - } - - [Fact] - public void TestNodeMatcher() - { - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE[blah]:HAS[@FOO[bar*],#something[else]]")); - Patch patch = new Patch(urlConfig, Command.Edit, urlConfig.config); - INodeMatcher matcher = patch.nodeMatcher; - - Assert.True(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "blah" }, - { "something", "else" }, - new TestConfigNode("FOO") - { - { "name", "barbar" }, - }, - })); - - Assert.False(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "blah" }, - })); - - Assert.False(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "blah" }, - { "something", "else" }, - })); - - Assert.False(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "blah" }, - new TestConfigNode("FOO") - { - { "name", "barbar" }, - }, - })); - - Assert.False(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "bleh" }, - { "something", "else" }, - new TestConfigNode("FOO") - { - { "name", "barbar" }, - }, - })); - - Assert.False(matcher.IsMatch(new TestConfigNode("NADE") - { - { "name", "blah" }, - { "something", "else" }, - new TestConfigNode("FOO") - { - { "name", "barbar" }, - }, - })); - } - } -} diff --git a/ModuleManagerTests/Patches/CopyPatchTest.cs b/ModuleManagerTests/Patches/CopyPatchTest.cs new file mode 100644 index 00000000..3b5e1b1e --- /dev/null +++ b/ModuleManagerTests/Patches/CopyPatchTest.cs @@ -0,0 +1,274 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; +using ModuleManager.Patches; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class CopyPatchTest + { + [Fact] + public void TestConstructor__urlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new CopyPatch(null, Substitute.For()); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestConstructor__nodeMatcherNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null); + }); + + Assert.Equal("nodeMatcher", ex.ParamName); + } + + [Fact] + public void TestUrlConfig() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode()); + CopyPatch patch = new CopyPatch(urlConfig, Substitute.For()); + + Assert.Same(urlConfig, patch.UrlConfig); + } + + [Fact] + public void TestNodeMatcher() + { + INodeMatcher nodeMatcher = Substitute.For(); + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher); + + Assert.Same(nodeMatcher, patch.NodeMatcher); + } + + [Fact] + public void TestApply() + { + UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + + UrlDir.UrlConfig urlConfig1 = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "foo", "bar" }, + }, file); + + UrlDir.UrlConfig urlConfig2 = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "foo", "bar" }, + }, file); + + UrlDir.UrlConfig urlConfig3 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + UrlDir.UrlConfig urlConfig4 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + + INodeMatcher nodeMatcher = Substitute.For(); + + nodeMatcher.IsMatch(urlConfig1.config).Returns(false); + nodeMatcher.IsMatch(urlConfig2.config).Returns(true); + nodeMatcher.IsMatch(urlConfig3.config).Returns(false); + nodeMatcher.IsMatch(urlConfig4.config).Returns(true); + + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") + { + { "@foo", "baz" }, + { "pqr", "stw" }, + }), nodeMatcher); + + IPatchProgress progress = Substitute.For(); + IBasicLogger logger = Substitute.For(); + + patch.Apply(file, progress, logger); + + Assert.Equal(6, file.configs.Count); + + Assert.Same(urlConfig1, file.configs[0]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "foo", "bar" }, + }, file.configs[0].config); + + Assert.Same(urlConfig2, file.configs[1]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "foo", "bar" }, + }, file.configs[1].config); + + Assert.Same(urlConfig3, file.configs[2]); + AssertNodesEqual(new ConfigNode("NODE"), file.configs[2].config); + + Assert.Same(urlConfig4, file.configs[3]); + AssertNodesEqual(new ConfigNode("NODE"), file.configs[3].config); + + AssertNodesEqual(new TestConfigNode("NODE") + { + { "foo", "baz" }, + { "pqr", "stw" }, + }, file.configs[4].config); + + AssertNodesEqual(new TestConfigNode("NODE") + { + { "pqr", "stw" }, + }, file.configs[5].config); + + Received.InOrder(delegate + { + progress.ApplyingCopy(urlConfig2, patch.UrlConfig); + progress.ApplyingCopy(urlConfig4, patch.UrlConfig); + }); + + progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); + progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); + + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + + [Fact] + public void TestApply__NameChanged() + { + UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "name", "000" }, + { "foo", "bar" }, + }, file); + + INodeMatcher nodeMatcher = Substitute.For(); + + nodeMatcher.IsMatch(urlConfig.config).Returns(true); + + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") + { + { "@name", "001" }, + { "@foo", "baz" }, + { "pqr", "stw" }, + }), nodeMatcher); + + IPatchProgress progress = Substitute.For(); + IBasicLogger logger = Substitute.For(); + + patch.Apply(file, progress, logger); + + Assert.Equal(2, file.configs.Count); + + Assert.Same(urlConfig, file.configs[0]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "name", "000" }, + { "foo", "bar" }, + }, file.configs[0].config); + + AssertNodesEqual(new TestConfigNode("NODE") + { + { "name", "001" }, + { "foo", "baz" }, + { "pqr", "stw" }, + }, file.configs[1].config); + + progress.Received().ApplyingCopy(urlConfig, patch.UrlConfig); + + progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); + progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); + + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + + [Fact] + public void TestApply__NameNotChanged() + { + UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "name", "000" }, + { "foo", "bar" }, + }, file); + + INodeMatcher nodeMatcher = Substitute.For(); + + nodeMatcher.IsMatch(urlConfig.config).Returns(true); + + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("+NODE") + { + { "@foo", "baz" }, + { "pqr", "stw" }, + }), nodeMatcher); + + IPatchProgress progress = Substitute.For(); + IBasicLogger logger = Substitute.For(); + + patch.Apply(file, progress, logger); + + Assert.Equal(1, file.configs.Count); + + Assert.Same(urlConfig, file.configs[0]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "name", "000" }, + { "foo", "bar" }, + }, file.configs[0].config); + + progress.Received().Error(patch.UrlConfig, "Error - when applying copy ghi/jkl/+NODE to abc/def/NODE - the copy needs to have a different name than the parent (use @name = xxx)"); + + progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); + progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); + progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); + + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + + [Fact] + public void TestApply__FileNull() + { + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(null, Substitute.For(), Substitute.For()); + }); + + Assert.Equal("file", ex.ParamName); + } + + [Fact] + public void TestApply__ProgressNull() + { + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestApply__LoggerNull() + { + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); + }); + + Assert.Equal("logger", ex.ParamName); + } + + private void AssertNodesEqual(ConfigNode expected, ConfigNode actual) + { + Assert.Equal(expected.ToString(), actual.ToString()); + } + } +} diff --git a/ModuleManagerTests/Patches/DeletePatchTest.cs b/ModuleManagerTests/Patches/DeletePatchTest.cs new file mode 100644 index 00000000..b75576b7 --- /dev/null +++ b/ModuleManagerTests/Patches/DeletePatchTest.cs @@ -0,0 +1,130 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; +using ModuleManager.Patches; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class DeletePatchTest + { + [Fact] + public void TestConstructor__urlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new DeletePatch(null, Substitute.For()); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestConstructor__nodeMatcherNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null); + }); + + Assert.Equal("nodeMatcher", ex.ParamName); + } + + [Fact] + public void TestUrlConfig() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode()); + DeletePatch patch = new DeletePatch(urlConfig, Substitute.For()); + + Assert.Same(urlConfig, patch.UrlConfig); + } + + [Fact] + public void TestNodeMatcher() + { + INodeMatcher nodeMatcher = Substitute.For(); + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher); + + Assert.Same(nodeMatcher, patch.NodeMatcher); + } + + [Fact] + public void TestApply() + { + UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + + UrlDir.UrlConfig urlConfig1 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + UrlDir.UrlConfig urlConfig2 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + UrlDir.UrlConfig urlConfig3 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + UrlDir.UrlConfig urlConfig4 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + + INodeMatcher nodeMatcher = Substitute.For(); + + nodeMatcher.IsMatch(urlConfig1.config).Returns(false); + nodeMatcher.IsMatch(urlConfig2.config).Returns(true); + nodeMatcher.IsMatch(urlConfig3.config).Returns(false); + nodeMatcher.IsMatch(urlConfig4.config).Returns(true); + + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!NODE")), nodeMatcher); + + IPatchProgress progress = Substitute.For(); + IBasicLogger logger = Substitute.For(); + + patch.Apply(file, progress, logger); + + Assert.Equal(new[] { urlConfig1, urlConfig3 }, file.configs); + + Received.InOrder(delegate + { + progress.ApplyingDelete(urlConfig2, patch.UrlConfig); + progress.ApplyingDelete(urlConfig4, patch.UrlConfig); + }); + + progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); + progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); + + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + + [Fact] + public void TestApply__FileNull() + { + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(null, Substitute.For(), Substitute.For()); + }); + + Assert.Equal("file", ex.ParamName); + } + + [Fact] + public void TestApply__ProgressNull() + { + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestApply__LoggerNull() + { + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); + }); + + Assert.Equal("logger", ex.ParamName); + } + } +} diff --git a/ModuleManagerTests/Patches/EditPatchTest.cs b/ModuleManagerTests/Patches/EditPatchTest.cs new file mode 100644 index 00000000..7db368e3 --- /dev/null +++ b/ModuleManagerTests/Patches/EditPatchTest.cs @@ -0,0 +1,235 @@ +using System; +using System.Collections.Generic; +using Xunit; +using NSubstitute; +using UnityEngine; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; +using ModuleManager.Patches; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class EditPatchTest + { + [Fact] + public void TestConstructor__urlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new EditPatch(null, Substitute.For()); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestConstructor__nodeMatcherNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null); + }); + + Assert.Equal("nodeMatcher", ex.ParamName); + } + + [Fact] + public void TestUrlConfig() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode()); + EditPatch patch = new EditPatch(urlConfig, Substitute.For()); + + Assert.Same(urlConfig, patch.UrlConfig); + } + + [Fact] + public void TestNodeMatcher() + { + INodeMatcher nodeMatcher = Substitute.For(); + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher); + + Assert.Same(nodeMatcher, patch.NodeMatcher); + } + + [Fact] + public void TestApply() + { + UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + + UrlDir.UrlConfig urlConfig1 = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "foo", "bar" }, + }, file); + + UrlDir.UrlConfig urlConfig2 = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "foo", "bar" }, + }, file); + + UrlDir.UrlConfig urlConfig3 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + UrlDir.UrlConfig urlConfig4 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + + INodeMatcher nodeMatcher = Substitute.For(); + + nodeMatcher.IsMatch(urlConfig1.config).Returns(false); + nodeMatcher.IsMatch(urlConfig2.config).Returns(true); + nodeMatcher.IsMatch(urlConfig3.config).Returns(false); + nodeMatcher.IsMatch(urlConfig4.config).Returns(true); + + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") + { + { "@foo", "baz" }, + { "pqr", "stw" }, + }), nodeMatcher); + + IPatchProgress progress = Substitute.For(); + IBasicLogger logger = Substitute.For(); + + patch.Apply(file, progress, logger); + + Assert.Equal(4, file.configs.Count); + + Assert.Same(urlConfig1, file.configs[0]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "foo", "bar" }, + }, file.configs[0].config); + + Assert.NotSame(urlConfig2, file.configs[1]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "foo", "baz" }, + { "pqr", "stw" }, + }, file.configs[1].config); + + Assert.Same(urlConfig3, file.configs[2]); + AssertNodesEqual(new ConfigNode("NODE"), file.configs[2].config); + + Assert.NotSame(urlConfig4, file.configs[3]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "pqr", "stw" }, + }, file.configs[3].config); + + Received.InOrder(delegate + { + progress.ApplyingUpdate(urlConfig2, patch.UrlConfig); + progress.ApplyingUpdate(urlConfig4, patch.UrlConfig); + }); + + progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); + progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); + + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + + [Fact] + public void TestApply__Loop() + { + UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "name", "000" }, + { "aaa", "1" }, + }, file); + + INodeMatcher nodeMatcher = Substitute.For(); + + nodeMatcher.IsMatch(Arg.Is(node => int.Parse(node.GetValue("aaa")) < 10)).Returns(true); + + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") + { + { "@aaa *", "2" }, + { "bbb", "002" }, + new ConfigNode("MM_PATCH_LOOP"), + }), nodeMatcher); + + IPatchProgress progress = Substitute.For(); + IBasicLogger logger = Substitute.For(); + + List modifiedUrlConfigs = new List(); + progress.ApplyingUpdate(Arg.Do(url => modifiedUrlConfigs.Add(url)), patch.UrlConfig); + + patch.Apply(file, progress, logger); + + Assert.Equal(1, file.configs.Count); + Assert.NotSame(urlConfig, file.configs[0]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "name", "000" }, + { "aaa", "16" }, + { "bbb", "002" }, + { "bbb", "002" }, + { "bbb", "002" }, + { "bbb", "002" }, + }, file.configs[0].config); + + Assert.Same(urlConfig, modifiedUrlConfigs[0]); + Assert.NotSame(urlConfig, modifiedUrlConfigs[1]); + Assert.NotSame(urlConfig, modifiedUrlConfigs[2]); + Assert.NotSame(urlConfig, modifiedUrlConfigs[3]); + + Received.InOrder(delegate + { + logger.Log(LogType.Log, "Looping on ghi/jkl/@NODE to abc/def/NODE"); + progress.ApplyingUpdate(urlConfig, patch.UrlConfig); + progress.ApplyingUpdate(modifiedUrlConfigs[1], patch.UrlConfig); + progress.ApplyingUpdate(modifiedUrlConfigs[2], patch.UrlConfig); + progress.ApplyingUpdate(modifiedUrlConfigs[3], patch.UrlConfig); + }); + + progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); + progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); + + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + + [Fact] + public void TestApply__FileNull() + { + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(null, Substitute.For(), Substitute.For()); + }); + + Assert.Equal("file", ex.ParamName); + } + + [Fact] + public void TestApply__ProgressNull() + { + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestApply__LoggerNull() + { + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); + }); + + Assert.Equal("logger", ex.ParamName); + } + + private void AssertNodesEqual(ConfigNode expected, ConfigNode actual) + { + Assert.Equal(expected.ToString(), actual.ToString()); + } + } +} diff --git a/ModuleManagerTests/Patches/PatchCompilerTest.cs b/ModuleManagerTests/Patches/PatchCompilerTest.cs new file mode 100644 index 00000000..178a6d7a --- /dev/null +++ b/ModuleManagerTests/Patches/PatchCompilerTest.cs @@ -0,0 +1,257 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; +using ModuleManager.Patches; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class PatchCompilerTest + { + private readonly IPatchProgress progress = Substitute.For(); + private readonly IBasicLogger logger = Substitute.For(); + private readonly UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + + [Fact] + public void TestCompilePatch__Edit() + { + UrlDir.UrlConfig patchUrlConfig = UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") + { + { "@bar", "bleh" }, + }); + + EditPatch patch = Assert.IsType(PatchCompiler.CompilePatch(patchUrlConfig, Command.Edit, "NODE[foo]:HAS[#bar]")); + + Assert.Same(patchUrlConfig, patch.UrlConfig); + AssertNodeMatcher(patch.NodeMatcher); + + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "name", "foo" }, + { "bar", "baz" }, + }, file); + + patch.Apply(file, progress, logger); + + AssertNoErrors(); + + progress.Received().ApplyingUpdate(urlConfig, patchUrlConfig); + + Assert.Equal(1, file.configs.Count); + Assert.NotSame(urlConfig, file.configs[0]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "name", "foo" }, + { "bar", "bleh" }, + }, file.configs[0].config); + } + + [Fact] + public void TestCompilePatch__Copy() + { + UrlDir.UrlConfig patchUrlConfig = UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("+NODE") + { + { "@name", "boo" }, + { "@bar", "bleh" }, + }); + + CopyPatch patch = Assert.IsType(PatchCompiler.CompilePatch(patchUrlConfig, Command.Copy, "NODE[foo]:HAS[#bar]")); + + Assert.Same(patchUrlConfig, patch.UrlConfig); + AssertNodeMatcher(patch.NodeMatcher); + + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "name", "foo" }, + { "bar", "baz" }, + }, file); + + patch.Apply(file, progress, logger); + + AssertNoErrors(); + + progress.Received().ApplyingCopy(urlConfig, patchUrlConfig); + + Assert.Equal(2, file.configs.Count); + Assert.Same(urlConfig, file.configs[0]); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "name", "foo" }, + { "bar", "baz" }, + }, file.configs[0].config); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "name", "boo" }, + { "bar", "bleh" }, + }, file.configs[1].config); + } + + [Fact] + public void TestCompilePatch__Delete() + { + UrlDir.UrlConfig patchUrlConfig = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("-NODE")); + + DeletePatch patch = Assert.IsType(PatchCompiler.CompilePatch(patchUrlConfig, Command.Delete, "NODE[foo]:HAS[#bar]")); + + Assert.Same(patchUrlConfig, patch.UrlConfig); + AssertNodeMatcher(patch.NodeMatcher); + + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + { + { "name", "foo" }, + { "bar", "baz" }, + }, file); + + patch.Apply(file, progress, logger); + + AssertNoErrors(); + + progress.Received().ApplyingDelete(urlConfig, patchUrlConfig); + + Assert.Equal(0, file.configs.Count); + } + + [Fact] + public void TestCompilePatch__NullUrlConfig() + { + ArgumentNullException ex = Assert.Throws(delegate + { + PatchCompiler.CompilePatch(null, Command.Edit, "NODE[foo]:HAS[#bar]"); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestCompilePatch__NullName() + { + ArgumentNullException ex = Assert.Throws(delegate + { + PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Edit, null); + }); + + Assert.Equal("name", ex.ParamName); + } + + [Fact] + public void TestCompilePatch__BlankName() + { + ArgumentException ex = Assert.Throws(delegate + { + PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Edit, ""); + }); + + Assert.Equal("name", ex.ParamName); + Assert.Equal("can't be empty\r\nParameter name: name", ex.Message); + } + + [Fact] + public void TestCompilePatch__InvalidCommand__Replace() + { + ArgumentException ex = Assert.Throws(delegate + { + PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Replace, "NODE[foo]:HAS[#bar]"); + }); + + Assert.Equal("command", ex.ParamName); + Assert.Equal("invalid command for a root node: Replace\r\nParameter name: command", ex.Message); + } + + [Fact] + public void TestCompilePatch__InvalidCommand__Create() + { + ArgumentException ex = Assert.Throws(delegate + { + PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Create, "NODE[foo]:HAS[#bar]"); + }); + + Assert.Equal("command", ex.ParamName); + Assert.Equal("invalid command for a root node: Create\r\nParameter name: command", ex.Message); + } + + [Fact] + public void TestCompilePatch__InvalidCommand__Rename() + { + ArgumentException ex = Assert.Throws(delegate + { + PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Rename, "NODE[foo]:HAS[#bar]"); + }); + + Assert.Equal("command", ex.ParamName); + Assert.Equal("invalid command for a root node: Rename\r\nParameter name: command", ex.Message); + } + + [Fact] + public void TestCompilePatch__InvalidCommand__Paste() + { + ArgumentException ex = Assert.Throws(delegate + { + PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Paste, "NODE[foo]:HAS[#bar]"); + }); + + Assert.Equal("command", ex.ParamName); + Assert.Equal("invalid command for a root node: Paste\r\nParameter name: command", ex.Message); + } + + [Fact] + public void TestCompilePatch__InvalidCommand__Special() + { + ArgumentException ex = Assert.Throws(delegate + { + PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Special, "NODE[foo]:HAS[#bar]"); + }); + + Assert.Equal("command", ex.ParamName); + Assert.Equal("invalid command for a root node: Special\r\nParameter name: command", ex.Message); + } + + private void AssertNodeMatcher(INodeMatcher matcher) + { + Assert.True(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "foo" }, + { "bar", "baz" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "foo" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "boo" }, + { "bar", "baz" }, + })); + + Assert.False(matcher.IsMatch(new ConfigNode("NODE"))); + + Assert.False(matcher.IsMatch(new TestConfigNode("NADE") + { + { "name", "foo" }, + { "bar", "baz" }, + })); + + Assert.False(matcher.IsMatch(new TestConfigNode("NODE") + { + { "name", "boo" }, + { "bar", "baz" }, + })); + } + + private void AssertNodesEqual(ConfigNode expected, ConfigNode actual) + { + Assert.Equal(expected.ToString(), actual.ToString()); + } + + private void AssertNoErrors() + { + progress.DidNotReceiveWithAnyArgs().Error(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + } +} From ffbabbbdbeb035211853584a4d7ed43832c58dd0 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 19 Apr 2018 00:43:24 -0700 Subject: [PATCH 216/342] Fix tests depending on line endings Since we're already asserting on the ParamName, we can trust that the 2nd part of the message will be there and not test it. --- ModuleManagerTests/NodeMatcherTest.cs | 5 +---- ModuleManagerTests/PassTest.cs | 2 +- ModuleManagerTests/PatchListTest.cs | 8 ++++---- ModuleManagerTests/Patches/PatchCompilerTest.cs | 12 ++++++------ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/ModuleManagerTests/NodeMatcherTest.cs b/ModuleManagerTests/NodeMatcherTest.cs index d1f1baf3..036770cb 100644 --- a/ModuleManagerTests/NodeMatcherTest.cs +++ b/ModuleManagerTests/NodeMatcherTest.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Xunit; using TestUtils; using ModuleManager; @@ -32,7 +29,7 @@ public void TestConstructor__Blank() }); Assert.Equal("nodeName", ex.ParamName); - Assert.Equal("can't be empty\r\nParameter name: nodeName", ex.Message); + Assert.Contains("can't be empty", ex.Message); } [Fact] diff --git a/ModuleManagerTests/PassTest.cs b/ModuleManagerTests/PassTest.cs index 9e712e49..d2cce882 100644 --- a/ModuleManagerTests/PassTest.cs +++ b/ModuleManagerTests/PassTest.cs @@ -36,7 +36,7 @@ public void TestConstructor__NameEmpty() new Pass(""); }); - Assert.Equal("can't be empty\r\nParameter name: name", ex.Message); + Assert.Contains("can't be empty", ex.Message); Assert.Equal("name", ex.ParamName); } diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index 474ef64d..0de0a2ea 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -137,7 +137,7 @@ public void TestHasMod__Blank() Assert.True(patchList.HasMod("")); }); - Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); + Assert.Contains("can't be empty", ex.Message); Assert.Equal("mod", ex.ParamName); } @@ -171,7 +171,7 @@ public void TestAddBeforePatch__ModBlank() patchList.AddBeforePatch("", Substitute.For()); }); - Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); + Assert.Contains("can't be empty", ex.Message); Assert.Equal("mod", ex.ParamName); } @@ -216,7 +216,7 @@ public void TestAddForPatch__ModBlank() patchList.AddForPatch("", Substitute.For()); }); - Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); + Assert.Contains("can't be empty", ex.Message); Assert.Equal("mod", ex.ParamName); } @@ -261,7 +261,7 @@ public void TestAddAfterPatch__ModBlank() patchList.AddAfterPatch("", Substitute.For()); }); - Assert.Equal("can't be empty\r\nParameter name: mod", ex.Message); + Assert.Contains("can't be empty", ex.Message); Assert.Equal("mod", ex.ParamName); } diff --git a/ModuleManagerTests/Patches/PatchCompilerTest.cs b/ModuleManagerTests/Patches/PatchCompilerTest.cs index 178a6d7a..0b401ef3 100644 --- a/ModuleManagerTests/Patches/PatchCompilerTest.cs +++ b/ModuleManagerTests/Patches/PatchCompilerTest.cs @@ -145,7 +145,7 @@ public void TestCompilePatch__BlankName() }); Assert.Equal("name", ex.ParamName); - Assert.Equal("can't be empty\r\nParameter name: name", ex.Message); + Assert.Contains("can't be empty", ex.Message); } [Fact] @@ -157,7 +157,7 @@ public void TestCompilePatch__InvalidCommand__Replace() }); Assert.Equal("command", ex.ParamName); - Assert.Equal("invalid command for a root node: Replace\r\nParameter name: command", ex.Message); + Assert.Contains("invalid command for a root node: Replace", ex.Message); } [Fact] @@ -169,7 +169,7 @@ public void TestCompilePatch__InvalidCommand__Create() }); Assert.Equal("command", ex.ParamName); - Assert.Equal("invalid command for a root node: Create\r\nParameter name: command", ex.Message); + Assert.Contains("invalid command for a root node: Create", ex.Message); } [Fact] @@ -181,7 +181,7 @@ public void TestCompilePatch__InvalidCommand__Rename() }); Assert.Equal("command", ex.ParamName); - Assert.Equal("invalid command for a root node: Rename\r\nParameter name: command", ex.Message); + Assert.Contains("invalid command for a root node: Rename", ex.Message); } [Fact] @@ -193,7 +193,7 @@ public void TestCompilePatch__InvalidCommand__Paste() }); Assert.Equal("command", ex.ParamName); - Assert.Equal("invalid command for a root node: Paste\r\nParameter name: command", ex.Message); + Assert.Contains("invalid command for a root node: Paste", ex.Message); } [Fact] @@ -205,7 +205,7 @@ public void TestCompilePatch__InvalidCommand__Special() }); Assert.Equal("command", ex.ParamName); - Assert.Equal("invalid command for a root node: Special\r\nParameter name: command", ex.Message); + Assert.Contains("invalid command for a root node: Special", ex.Message); } private void AssertNodeMatcher(INodeMatcher matcher) From c91258096cc0c5d3fb79a08ccfa30eee950e8939 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 3 Jul 2018 23:33:31 -0700 Subject: [PATCH 217/342] Whitespace --- ModuleManager/Progress/ProgressCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Progress/ProgressCounter.cs b/ModuleManager/Progress/ProgressCounter.cs index 1400ad4f..c62d5c81 100644 --- a/ModuleManager/Progress/ProgressCounter.cs +++ b/ModuleManager/Progress/ProgressCounter.cs @@ -12,7 +12,7 @@ public class ProgressCounter public readonly Counter errors = new Counter(); public readonly Counter exceptions = new Counter(); public readonly Counter needsUnsatisfied = new Counter(); - + public readonly Dictionary errorFiles = new Dictionary(); } } From 7b230976b88f11380d84ad3d6f224d0c6a9042b5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 3 Jul 2018 23:38:45 -0700 Subject: [PATCH 218/342] Allow warnings Cache generation can proceed but the user will be alerted. --- ModuleManager/MMPatchLoader.cs | 8 ++++++++ ModuleManager/Progress/IPatchProgress.cs | 1 + ModuleManager/Progress/PatchProgress.cs | 19 +++++++++++++++++++ ModuleManager/Progress/ProgressCounter.cs | 2 ++ .../Progress/PatchProgressTest.cs | 19 +++++++++++++++++++ 5 files changed, 49 insertions(+) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 5da80d98..7c17bd27 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -258,6 +258,11 @@ float updateTimeRemaining() #region Saving Cache + foreach (KeyValuePair item in progress.Counter.warningFiles) + { + logger.Warning(item.Value + " warning" + (item.Value > 1 ? "s" : "") + " related to GameData/" + item.Key); + } + if (progress.Counter.errors > 0 || progress.Counter.exceptions > 0) { foreach (KeyValuePair item in progress.Counter.errorFiles) @@ -693,6 +698,9 @@ private void StatusUpdate(IPatchProgress progress) status = "ModuleManager: " + progress.Counter.patchedNodes + " patch" + (progress.Counter.patchedNodes != 1 ? "es" : "") + " applied"; + if (progress.Counter.warnings > 0) + status += ", found " + progress.Counter.warnings + " warning" + (progress.Counter.warnings != 1 ? "s" : "") + ""; + if (progress.Counter.errors > 0) status += ", found " + progress.Counter.errors + " error" + (progress.Counter.errors != 1 ? "s" : "") + ""; diff --git a/ModuleManager/Progress/IPatchProgress.cs b/ModuleManager/Progress/IPatchProgress.cs index 5eabfc3c..3b6f13df 100644 --- a/ModuleManager/Progress/IPatchProgress.cs +++ b/ModuleManager/Progress/IPatchProgress.cs @@ -9,6 +9,7 @@ public interface IPatchProgress float ProgressFraction { get; } + void Warning(UrlDir.UrlConfig url, string message); void Error(UrlDir.UrlConfig url, string message); void Exception(string message, Exception exception); void Exception(UrlDir.UrlConfig url, string message, Exception exception); diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index 4939ffc6..9f1f6cfa 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -95,6 +95,13 @@ public void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url) Counter.needsUnsatisfied.Increment(); } + public void Warning(UrlDir.UrlConfig url, string message) + { + Counter.warnings.Increment(); + logger.Warning(message); + RecordWarningFile(url); + } + public void Error(UrlDir.UrlConfig url, string message) { Counter.errors.Increment(); @@ -114,6 +121,18 @@ public void Exception(UrlDir.UrlConfig url, string message, Exception exception) RecordErrorFile(url); } + private void RecordWarningFile(UrlDir.UrlConfig url) + { + string key = url.parent.url + "." + url.parent.fileExtension; + if (key[0] == '/') + key = key.Substring(1); + + if (Counter.warningFiles.ContainsKey(key)) + Counter.warningFiles[key] += 1; + else + Counter.warningFiles[key] = 1; + } + private void RecordErrorFile(UrlDir.UrlConfig url) { string key = url.parent.url + "." + url.parent.fileExtension; diff --git a/ModuleManager/Progress/ProgressCounter.cs b/ModuleManager/Progress/ProgressCounter.cs index c62d5c81..7fc16627 100644 --- a/ModuleManager/Progress/ProgressCounter.cs +++ b/ModuleManager/Progress/ProgressCounter.cs @@ -9,10 +9,12 @@ public class ProgressCounter public readonly Counter totalPatches = new Counter(); public readonly Counter appliedPatches = new Counter(); public readonly Counter patchedNodes = new Counter(); + public readonly Counter warnings = new Counter(); public readonly Counter errors = new Counter(); public readonly Counter exceptions = new Counter(); public readonly Counter needsUnsatisfied = new Counter(); + public readonly Dictionary warningFiles = new Dictionary(); public readonly Dictionary errorFiles = new Dictionary(); } } diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index d94ae87e..cf650285 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -219,6 +219,25 @@ public void TestNeedsUnsatisfiedAfter() logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); } + [Fact] + public void TestWarning() + { + UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_OTHER_NODE")); + + Assert.Equal(0, progress.Counter.warnings); + + progress.Warning(config1, "I'm warning you"); + Assert.Equal(1, progress.Counter.warnings); + Assert.Equal(1, progress.Counter.warningFiles["abc/def.cfg"]); + logger.Received().Log(LogType.Warning, "I'm warning you"); + + progress.Warning(config2, "You should probably pay attention to this"); + Assert.Equal(2, progress.Counter.warnings); + Assert.Equal(2, progress.Counter.warningFiles["abc/def.cfg"]); + logger.Received().Log(LogType.Warning, "You should probably pay attention to this"); + } + [Fact] public void TestError() { From 99984913ac564b5a06860a4094bbafe4ff335450 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 3 Jul 2018 23:39:52 -0700 Subject: [PATCH 219/342] Make ArrayEnumerator more versatile Now optionally accepts start index and length --- ModuleManager/Collections/ArrayEnumerator.cs | 35 ++++++- .../Collections/ArrayEnumeratorTest.cs | 91 +++++++++++++++++++ 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/ModuleManager/Collections/ArrayEnumerator.cs b/ModuleManager/Collections/ArrayEnumerator.cs index c965b9d3..775a35dd 100644 --- a/ModuleManager/Collections/ArrayEnumerator.cs +++ b/ModuleManager/Collections/ArrayEnumerator.cs @@ -7,12 +7,37 @@ namespace ModuleManager.Collections public struct ArrayEnumerator : IEnumerator { private readonly T[] array; + private readonly int startIndex; + private readonly int length; + private int index; - public ArrayEnumerator(params T[] array) + public ArrayEnumerator(params T[] array) : this(array, 0) { } + + public ArrayEnumerator(T[] array, int startIndex) : this(array, startIndex, (array?.Length ?? -1) - startIndex) { } + + public ArrayEnumerator(T[] array, int startIndex, int length) { - this.array = array; - index = -1; + this.array = array ?? throw new ArgumentNullException(nameof(array)); + + if (startIndex < 0) + throw new ArgumentException($"must be non-negative (got {startIndex})", nameof(startIndex)); + if (startIndex > array.Length) + throw new ArgumentException( + $"must be less than or equal to array length (array length {array.Length}, startIndex {startIndex})", + nameof(startIndex) + ); + if (length < 0) + throw new ArgumentException($"must be non-negative (got {length})", nameof(length)); + if (startIndex + length > array.Length) + throw new ArgumentException( + $"must fit within the string (array length {array.Length}, startIndex {startIndex}, length {length})", + nameof(length) + ); + + this.startIndex = startIndex; + this.length = length; + index = startIndex - 1; } public T Current => array[index]; @@ -23,12 +48,12 @@ public void Dispose() { } public bool MoveNext() { index++; - return index < array.Length; + return index < startIndex + length; } public void Reset() { - index = -1; + index = startIndex - 1; } } } diff --git a/ModuleManagerTests/Collections/ArrayEnumeratorTest.cs b/ModuleManagerTests/Collections/ArrayEnumeratorTest.cs index 34eac1ed..7ba5cf64 100644 --- a/ModuleManagerTests/Collections/ArrayEnumeratorTest.cs +++ b/ModuleManagerTests/Collections/ArrayEnumeratorTest.cs @@ -7,6 +7,69 @@ namespace ModuleManagerTests.Collections { public class ArrayEnumeratorTest { + [Fact] + public void Test__Constructor__ArrayNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new ArrayEnumerator(null); + }); + + Assert.Equal("array", ex.ParamName); + } + + [Fact] + public void Test__Constructor__StartIndex__Negative() + { + string[] arr = { "abc", "def", "ghi" }; + ArgumentException ex = Assert.Throws(delegate + { + new ArrayEnumerator(arr, -1); + }); + + Assert.Equal("startIndex", ex.ParamName); + Assert.Contains("must be non-negative (got -1)", ex.Message); + } + + [Fact] + public void Test__Constructor__StartIndex__TooLarge() + { + string[] arr = { "abc", "def", "ghi" }; + ArgumentException ex = Assert.Throws(delegate + { + new ArrayEnumerator(arr, 4); + }); + + Assert.Equal("startIndex", ex.ParamName); + Assert.Contains("must be less than or equal to array length (array length 3, startIndex 4)", ex.Message); + } + + [Fact] + public void Test__Constructor__Length__Negative() + { + string[] arr = { "abc", "def", "ghi" }; + ArgumentException ex = Assert.Throws(delegate + { + new ArrayEnumerator(arr, 1, -1); + }); + + Assert.Equal("length", ex.ParamName); + Assert.Contains("must be non-negative (got -1)", ex.Message); + } + + [Fact] + public void Test__Constructor__Length__TooLong() + { + string[] arr = { "abc", "def", "ghi" }; + ArgumentException ex = Assert.Throws(delegate + { + new ArrayEnumerator(arr, 1, 3); + }); + + Assert.Equal("length", ex.ParamName); + Assert.Contains("must fit within the string (array length 3, startIndex 1, length 3)", ex.Message); + } + [Fact] public void TestArrayEnumerator() { @@ -52,5 +115,33 @@ public void TestArrayEnumerator__Empty() IEnumerator enumerator = new ArrayEnumerator(arr); Assert.False(enumerator.MoveNext()); } + + [Fact] + public void TestArrayEnumerator__StartIndex() + { + string[] arr = { "abc", "def", "ghi" }; + + IEnumerator enumerator = new ArrayEnumerator(arr, 1); + + Assert.True(enumerator.MoveNext()); + Assert.Equal("def", enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal("ghi", enumerator.Current); + Assert.False(enumerator.MoveNext()); + } + + [Fact] + public void TestArrayEnumerator__StartIndex__Length() + { + string[] arr = { "abc", "def", "ghi", "jkl" }; + + IEnumerator enumerator = new ArrayEnumerator(arr, 1, 2); + + Assert.True(enumerator.MoveNext()); + Assert.Equal("def", enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal("ghi", enumerator.Current); + Assert.False(enumerator.MoveNext()); + } } } From 7d994ad71842dd85e4c48efcdeed075d8ed09dd3 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 3 Jul 2018 23:53:23 -0700 Subject: [PATCH 220/342] Tag, TagList, TagListParser A lot of things in MM are structured like :tag[value]trailer and this formalizes that structure --- ModuleManager/ModuleManager.csproj | 3 + ModuleManager/Tags/Tag.cs | 34 +++ ModuleManager/Tags/TagList.cs | 30 +++ ModuleManager/Tags/TagListParser.cs | 160 ++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 3 + ModuleManagerTests/Tags/TagListParserTest.cs | 253 +++++++++++++++++++ ModuleManagerTests/Tags/TagListTest.cs | 44 ++++ ModuleManagerTests/Tags/TagTest.cs | 145 +++++++++++ 8 files changed, 672 insertions(+) create mode 100644 ModuleManager/Tags/Tag.cs create mode 100644 ModuleManager/Tags/TagList.cs create mode 100644 ModuleManager/Tags/TagListParser.cs create mode 100644 ModuleManagerTests/Tags/TagListParserTest.cs create mode 100644 ModuleManagerTests/Tags/TagListTest.cs create mode 100644 ModuleManagerTests/Tags/TagTest.cs diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 0bb5a934..3468f389 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -75,6 +75,9 @@ + + + diff --git a/ModuleManager/Tags/Tag.cs b/ModuleManager/Tags/Tag.cs new file mode 100644 index 00000000..173058a9 --- /dev/null +++ b/ModuleManager/Tags/Tag.cs @@ -0,0 +1,34 @@ +using System; + +namespace ModuleManager.Tags +{ + public struct Tag + { + public readonly string key; + public readonly string value; + public readonly string trailer; + + public Tag(string key, string value, string trailer) + { + this.key = key ?? throw new ArgumentNullException(nameof(key)); + if (key == string.Empty) throw new ArgumentException("can't be empty", nameof(key)); + + if (value == null && trailer != null) + throw new ArgumentException("trailer must be null if value is null"); + + if (trailer == string.Empty) throw new ArgumentException("can't be empty (null allowed)", nameof(trailer)); + + this.value = value; + this.trailer = trailer; + } + + public override string ToString() + { + string s = "< '" + key + "' "; + if (value != null) s += "[ '" + value + "' ] "; + if (trailer != null) s += "'" + trailer + "' "; + s += ">"; + return s; + } + } +} diff --git a/ModuleManager/Tags/TagList.cs b/ModuleManager/Tags/TagList.cs new file mode 100644 index 00000000..0744c411 --- /dev/null +++ b/ModuleManager/Tags/TagList.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using ModuleManager.Collections; + +namespace ModuleManager.Tags +{ + public interface ITagList : IEnumerable + { + Tag PrimaryTag { get; } + } + + public class TagList : ITagList + { + private readonly Tag[] tags; + + public TagList(Tag primaryTag, IEnumerable tags) + { + PrimaryTag = primaryTag; + this.tags = tags?.ToArray() ?? throw new ArgumentNullException(nameof(tags)); + } + + public Tag PrimaryTag { get; private set; } + + public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(tags); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/ModuleManager/Tags/TagListParser.cs b/ModuleManager/Tags/TagListParser.cs new file mode 100644 index 00000000..b714240e --- /dev/null +++ b/ModuleManager/Tags/TagListParser.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; + +namespace ModuleManager.Tags +{ + public interface ITagListParser + { + ITagList Parse(string ToParse); + } + + public class TagListParser : ITagListParser + { + public ITagList Parse(string toParse) + { + if (toParse == null) throw new ArgumentNullException(nameof(toParse)); + if (toParse.Length == 0) throw new FormatException("can't create tag list from empty string"); + if (toParse[0] == '[') throw new FormatException("can't create tag list beginning with ["); + if (toParse[0] == ':') throw new FormatException("can't create tag list beginning with :"); + if (toParse[toParse.Length - 1] == ':') throw new FormatException("tag list can't end with :"); + + List tags = new List(); + Tag primaryTag = ParsePrimaryTag(toParse, ref tags); + return new TagList(primaryTag, tags); + } + + private static Tag ParsePrimaryTag(string toParse, ref List tags) + { + for (int i = 1; i < toParse.Length; i++) + { + char c = toParse[i]; + + if (c == '[') + { + int j = ClosingBracketIndex(toParse, i + 1); + return ParsePrimaryTrailer(toParse, j + 1, ref tags, toParse.Substring(0, i), toParse.Substring(i + 1, j - i - 1)); + } + else if (c == ':') + { + ParseTag(toParse, i + 1, ref tags); + return new Tag(toParse.Substring(0, i), null, null); + } + else if (c == ']') + { + throw new FormatException("encountered closing bracket in primary key"); + } + } + + return new Tag(toParse, null, null); + } + + private static Tag ParsePrimaryTrailer(string toParse, int start, ref List tags, string primaryKey, string primaryValue) + { + for (int i = start; i < toParse.Length; i++) + { + char c = toParse[i]; + + if (c == ':') + { + string trailer = i == start ? null : toParse.Substring(start, i - start); + ParseTag(toParse, i + 1, ref tags); + return new Tag(primaryKey, primaryValue, trailer); + } + else if (c == '[') + { + throw new FormatException("encountered opening bracket in primary trailer"); + } + else if (c == ']') + { + throw new FormatException("encountered closing bracket in primary trailer"); + } + } + + string primaryTrailer = toParse.Length - start == 0 ? null : toParse.Substring(start); + return new Tag(primaryKey, primaryValue, primaryTrailer); + } + + private static void ParseTag(string toParse, int start, ref List tags) + { + for (int i = start; i < toParse.Length; i++) + { + char c = toParse[i]; + + if (c == '[') + { + if (i == start) + throw new FormatException("tag can't start with ["); + + int j = ClosingBracketIndex(toParse, i + 1); + ParseTrailer(toParse, j + 1, ref tags, toParse.Substring(start, i - start), toParse.Substring(i + 1, j - i - 1)); + return; + } + else if (c == ':') + { + if (i == start) + throw new FormatException("tag can't start with :"); + + tags.Add(new Tag(toParse.Substring(start, i - start), null, null)); + ParseTag(toParse, i + 1, ref tags); + return; + } + else if (c == ']') + { + throw new FormatException("encountered closing bracket in key"); + } + } + + tags.Add(new Tag(toParse.Substring(start), null, null)); + } + + private static void ParseTrailer(string toParse, int start, ref List tags, string key, string value) + { + for (int i = start; i < toParse.Length; i++) + { + char c = toParse[i]; + + if (c == ':') + { + string trailer = i == start ? null : toParse.Substring(start, i - start); + tags.Add(new Tag(key, value, trailer)); + ParseTag(toParse, i + 1, ref tags); + return; + } + else if (c == '[') + { + throw new FormatException("encountered opening bracket in trailer"); + } + else if (c == ']') + { + throw new FormatException("encountered closing bracket in trailer"); + } + } + + string finalTrailer = toParse.Length - start == 0 ? null : toParse.Substring(start); + tags.Add(new Tag(key, value, finalTrailer)); + } + + private static int ClosingBracketIndex(string toParse, int start) + { + int bracketLevel = 0; + + for (int i = start; i < toParse.Length; i++) + { + char c = toParse[i]; + + if (c == '[') + { + bracketLevel++; + } + else if (c == ']') + { + bracketLevel--; + } + + if (bracketLevel == -1) return i; + } + + throw new FormatException("reached end of the tag list without encountering a close bracket"); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 87d7ec27..7a2d4e8c 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -79,6 +79,9 @@ + + + diff --git a/ModuleManagerTests/Tags/TagListParserTest.cs b/ModuleManagerTests/Tags/TagListParserTest.cs new file mode 100644 index 00000000..b13277e8 --- /dev/null +++ b/ModuleManagerTests/Tags/TagListParserTest.cs @@ -0,0 +1,253 @@ +using System; +using Xunit; +using ModuleManager.Tags; + +namespace ModuleManagerTests.Tags +{ + public class TagListParserTest + { + private readonly TagListParser tagListParser = new TagListParser(); + + [Fact] + public void TestParse__OnlyPrimaryKey() + { + ITagList tagList = tagListParser.Parse("01"); + Assert.Equal(new Tag("01", null, null), tagList.PrimaryTag); + Assert.Empty(tagList); + } + + [Fact] + public void TestParse__OnlyPrimaryKeyAndValue() + { + ITagList tagList = tagListParser.Parse("01[02]"); + Assert.Equal(new Tag("01", "02", null), tagList.PrimaryTag); + Assert.Empty(tagList); + } + + [Fact] + public void TestParse__OnlyPrimaryKeyValueAndTrailer() + { + ITagList tagList = tagListParser.Parse("01[02]03"); + Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); + Assert.Empty(tagList); + } + + [Fact] + public void TestParse__OnlyPrimaryKeyValueAndTrailer__ValueHasSomeStuff() + { + ITagList tagList = tagListParser.Parse("01[02:[03:04[05]]]06"); + Assert.Equal(new Tag("01", "02:[03:04[05]]", "06"), tagList.PrimaryTag); + Assert.Empty(tagList); + } + + [Fact] + public void TestParse__TagWithOnlyKey() + { + ITagList tagList = tagListParser.Parse("01[02]03:04:05"); + Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); + Assert.Equal(new[] { + new Tag("04", null, null), + new Tag("05", null, null), + }, tagList); + } + + [Fact] + public void TestParse__TagWithOnlyKeyAndValue() + { + ITagList tagList = tagListParser.Parse("01[02]03:04[05]:06[07]"); + Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); + Assert.Equal(new[] { + new Tag("04", "05", null), + new Tag("06", "07", null), + }, tagList); + } + + [Fact] + public void TestParse__FullTags() + { + ITagList tagList = tagListParser.Parse("01[02]03:04[05]06:07[08]09"); + Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); + Assert.Equal(new[] { + new Tag("04", "05", "06"), + new Tag("07", "08", "09"), + }, tagList); + } + + [Fact] + public void TestParse__MixedTags() + { + ITagList tagList = tagListParser.Parse("01[02]03:04:05[06]:07[08]09"); + Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); + Assert.Equal(new[] { + new Tag("04", null, null), + new Tag("05", "06", null), + new Tag("07", "08", "09"), + }, tagList); + } + + [Fact] + public void TestParse__Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + tagListParser.Parse(null); + }); + + Assert.Equal("toParse", ex.ParamName); + } + + [Fact] + public void TestParse__Empty() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse(""); + }); + + Assert.Equal("can't create tag list from empty string", ex.Message); + } + + [Fact] + public void TestParse__StartsWithOpenBracket() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("[stuff]"); + }); + + Assert.Equal("can't create tag list beginning with [", ex.Message); + } + + [Fact] + public void TestParse__StartsWithColon() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse(":stuff"); + }); + + Assert.Equal("can't create tag list beginning with :", ex.Message); + } + + [Fact] + public void TestParse__EndsWithColon() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("stuff:blah:"); + }); + + Assert.Equal("tag list can't end with :", ex.Message); + } + + [Fact] + public void TestParse__ClosingBracketInPrimaryKey() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc]def"); + }); + + Assert.Equal("encountered closing bracket in primary key", ex.Message); + } + + [Fact] + public void TestParse__PrimaryValueHasNoClosingBracket() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc[def[ghi]jkl"); + }); + + Assert.Equal("reached end of the tag list without encountering a close bracket", ex.Message); + } + + [Fact] + public void TestParse__OpeningBracketInPrimaryTrailer() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc[def]ghi[jkl]"); + }); + + Assert.Equal("encountered opening bracket in primary trailer", ex.Message); + } + + [Fact] + public void TestParse__ClosingBracketInPrimaryTrailer() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc[def]ghi]jkl"); + }); + + Assert.Equal("encountered closing bracket in primary trailer", ex.Message); + } + + [Fact] + public void TestParse__TagStartsWithOpenBracket() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc:def:[ghi]"); + }); + + Assert.Equal("tag can't start with [", ex.Message); + } + + [Fact] + public void TestParse__TagStartsWithColon() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc:def::ghi"); + }); + + Assert.Equal("tag can't start with :", ex.Message); + } + + [Fact] + public void TestParse__ClosingBracketInKey() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc:def:ghi]jkl"); + }); + + Assert.Equal("encountered closing bracket in key", ex.Message); + } + + [Fact] + public void TestParse__ValueHasNoClosingBracket() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc:def:ghi[jkl[mno]pqr"); + }); + + Assert.Equal("reached end of the tag list without encountering a close bracket", ex.Message); + } + + [Fact] + public void TestParse__OpeningBracketInTrailer() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc:def:ghi[jkl]mno[pqr]"); + }); + + Assert.Equal("encountered opening bracket in trailer", ex.Message); + } + + [Fact] + public void TestParse__ClosingBracketInTrailer() + { + FormatException ex = Assert.Throws(delegate + { + tagListParser.Parse("abc:def:ghi[jkl]mno]pqr"); + }); + + Assert.Equal("encountered closing bracket in trailer", ex.Message); + } + } +} diff --git a/ModuleManagerTests/Tags/TagListTest.cs b/ModuleManagerTests/Tags/TagListTest.cs new file mode 100644 index 00000000..0d3d40dd --- /dev/null +++ b/ModuleManagerTests/Tags/TagListTest.cs @@ -0,0 +1,44 @@ +using System; +using Xunit; +using ModuleManager.Tags; + +namespace ModuleManagerTests.Tags +{ + public class TagListTest + { + [Fact] + public void TestPrimaryTag() + { + Tag primaryTag = new Tag("stuff", null, null); + TagList tagList = new TagList(primaryTag, new Tag[0]); + + Assert.Equal(primaryTag, tagList.PrimaryTag); + } + + [Fact] + public void TestEnumeration() + { + Tag primaryTag = new Tag("stuff", null, null); + Tag tag1 = new Tag("tag1", null, null); + Tag tag2 = new Tag("tag2", null, null); + + Tag[] tags = new Tag[] { tag1, tag2 }; + TagList tagList = new TagList(primaryTag, tags); + + tags[0] = new Tag("tag3", null, null); + + Assert.Equal(new[] { tag1, tag2 }, tagList); + } + + [Fact] + public void TestConstructor__TagsNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new TagList(new Tag("blah", null, null), null); + }); + + Assert.Equal("tags", ex.ParamName); + } + } +} diff --git a/ModuleManagerTests/Tags/TagTest.cs b/ModuleManagerTests/Tags/TagTest.cs new file mode 100644 index 00000000..fec92b91 --- /dev/null +++ b/ModuleManagerTests/Tags/TagTest.cs @@ -0,0 +1,145 @@ +using System; +using Xunit; +using ModuleManager.Tags; + +namespace ModuleManagerTests.Tags +{ + public class TagTest + { + [Fact] + public void Test__OnlyKey() + { + Tag tag = new Tag("key", null, null); + + Assert.Equal("key", tag.key); + Assert.Null(tag.value); + Assert.Null(tag.trailer); + } + + [Fact] + public void Test__KeyAndValue() + { + Tag tag = new Tag("key", "value", null); + + Assert.Equal("key", tag.key); + Assert.Equal("value", tag.value); + Assert.Null(tag.trailer); + } + + [Fact] + public void Test__KeyAndEmptyValue() + { + Tag tag = new Tag("key", "", null); + + Assert.Equal("key", tag.key); + Assert.Equal("", tag.value); + Assert.Null(tag.trailer); + } + + [Fact] + public void Test__KeyValueAndTrailer() + { + Tag tag = new Tag("key", "value", "trailer"); + + Assert.Equal("key", tag.key); + Assert.Equal("value", tag.value); + Assert.Equal("trailer", tag.trailer); + } + + [Fact] + public void Test__KeyEmptyValueAndTrailer() + { + Tag tag = new Tag("key", "", "trailer"); + + Assert.Equal("key", tag.key); + Assert.Equal("", tag.value); + Assert.Equal("trailer", tag.trailer); + } + + [Fact] + public void TestConstructor__KeyNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new Tag(null, "value", "trailer"); + }); + + Assert.Equal("key", ex.ParamName); + } + + [Fact] + public void TestConstructor__KeyEmpty() + { + ArgumentException ex = Assert.Throws(delegate + { + new Tag("", "value", "trailer"); + }); + + Assert.Equal("key", ex.ParamName); + Assert.Contains("can't be empty", ex.Message); + } + + [Fact] + public void TestConstructor__ValueNullButTrailerNotNull() + { + ArgumentException ex = Assert.Throws(delegate + { + new Tag("key", null, "trailer"); + }); + + Assert.Contains("trailer must be null if value is null", ex.Message); + } + + [Fact] + public void TestConstructor__TrailerEmpty() + { + ArgumentException ex = Assert.Throws(delegate + { + new Tag("key", "value", ""); + }); + + Assert.Equal("trailer", ex.ParamName); + Assert.Contains("can't be empty (null allowed)", ex.Message); + } + + [Fact] + public void TestToString__Key() + { + Tag tag = new Tag("key", null, null); + + Assert.Equal("< 'key' >", tag.ToString()); + } + + [Fact] + public void TestToString__KeyAndValue() + { + Tag tag = new Tag("key", "value", null); + + Assert.Equal("< 'key' [ 'value' ] >", tag.ToString()); + } + + [Fact] + public void TestToString__KeyAndEmptyValue() + { + Tag tag = new Tag("key", "", null); + + Assert.Equal("< 'key' [ '' ] >", tag.ToString()); + } + + [Fact] + public void TestToString__KeyValueAndTrailer() + { + Tag tag = new Tag("key", "value", "trailer"); + + Assert.Equal("< 'key' [ 'value' ] 'trailer' >", tag.ToString()); + } + + [Fact] + public void TestToString__KeyEmptyValueAndTrailer() + { + Tag tag = new Tag("key", "", "trailer"); + + Assert.Equal("< 'key' [ '' ] 'trailer' >", tag.ToString()); + } + } +} From 9b79b392f325efd89949a41c920f4871946a2ea5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 4 Jul 2018 00:25:42 -0700 Subject: [PATCH 221/342] Restructure patch building Lots of changes * Parsing/validating patch is now separate code * Less code in the patch extractor (may even be able to go away entirely with some simplifications) * Pass specifier is now an explicit concept * Needs checker is now an object and has a cleaner interface * Some things which were errors before are now just warnings * If there is more than one pass specifier it will take the first one and warn * Syntax for root patch names is much more formal now, this might break some unusual cases that are silently accepted now --- ModuleManager/MMPatchLoader.cs | 40 +- ModuleManager/ModuleManager.csproj | 10 + ModuleManager/NeedsChecker.cs | 227 ++- ModuleManager/NodeMatcher.cs | 46 +- ModuleManager/PatchApplier.cs | 36 +- ModuleManager/PatchExtractor.cs | 201 +-- ModuleManager/PatchList.cs | 100 +- ModuleManager/Patches/CopyPatch.cs | 5 +- ModuleManager/Patches/DeletePatch.cs | 5 +- ModuleManager/Patches/EditPatch.cs | 5 +- ModuleManager/Patches/IPatch.cs | 2 + .../PassSpecifiers/AfterPassSpecifier.cs | 29 + .../PassSpecifiers/BeforePassSpecifier.cs | 29 + .../PassSpecifiers/FinalPassSpecifier.cs | 17 + .../PassSpecifiers/FirstPassSpecifier.cs | 17 + .../PassSpecifiers/ForPassSpecifier.cs | 29 + .../Patches/PassSpecifiers/IPassSpecifier.cs | 11 + .../PassSpecifiers/InsertPassSpecifier.cs | 17 + .../PassSpecifiers/LegacyPassSpecifier.cs | 17 + ModuleManager/Patches/PatchCompiler.cs | 25 +- ModuleManager/Patches/ProtoPatch.cs | 27 + ModuleManager/Patches/ProtoPatchBuilder.cs | 224 +++ ModuleManager/Progress/IPatchProgress.cs | 4 +- ModuleManager/Progress/PatchProgress.cs | 8 +- ModuleManagerTests/ModuleManagerTests.csproj | 8 + ModuleManagerTests/NeedsCheckerTest.cs | 647 +++------ ModuleManagerTests/NodeMatcherTest.cs | 51 +- ModuleManagerTests/PatchApplierTest.cs | 120 +- ModuleManagerTests/PatchExtractorTest.cs | 830 ++++------- ModuleManagerTests/PatchListTest.cs | 328 ++--- ModuleManagerTests/Patches/CopyPatchTest.cs | 41 +- ModuleManagerTests/Patches/DeletePatchTest.cs | 37 +- ModuleManagerTests/Patches/EditPatchTest.cs | 39 +- .../PassSpecifiers/AfterPassSpecifierTest.cs | 105 ++ .../PassSpecifiers/BeforePassSpecifierTest.cs | 105 ++ .../PassSpecifiers/FinalPassSpecifierTest.cs | 52 + .../PassSpecifiers/FirstPassSpecifierTest.cs | 52 + .../PassSpecifiers/ForPassSpecifierTest.cs | 105 ++ .../PassSpecifiers/InsertPassSpecifierTest.cs | 52 + .../PassSpecifiers/LegacyPassSpecifierTest.cs | 52 + .../Patches/PatchCompilerTest.cs | 175 ++- .../Patches/ProtoPatchBuilderTest.cs | 1231 +++++++++++++++++ .../Progress/PatchProgressTest.cs | 20 +- 43 files changed, 3343 insertions(+), 1838 deletions(-) create mode 100644 ModuleManager/Patches/PassSpecifiers/AfterPassSpecifier.cs create mode 100644 ModuleManager/Patches/PassSpecifiers/BeforePassSpecifier.cs create mode 100644 ModuleManager/Patches/PassSpecifiers/FinalPassSpecifier.cs create mode 100644 ModuleManager/Patches/PassSpecifiers/FirstPassSpecifier.cs create mode 100644 ModuleManager/Patches/PassSpecifiers/ForPassSpecifier.cs create mode 100644 ModuleManager/Patches/PassSpecifiers/IPassSpecifier.cs create mode 100644 ModuleManager/Patches/PassSpecifiers/InsertPassSpecifier.cs create mode 100644 ModuleManager/Patches/PassSpecifiers/LegacyPassSpecifier.cs create mode 100644 ModuleManager/Patches/ProtoPatch.cs create mode 100644 ModuleManager/Patches/ProtoPatchBuilder.cs create mode 100644 ModuleManagerTests/Patches/PassSpecifiers/AfterPassSpecifierTest.cs create mode 100644 ModuleManagerTests/Patches/PassSpecifiers/BeforePassSpecifierTest.cs create mode 100644 ModuleManagerTests/Patches/PassSpecifiers/FinalPassSpecifierTest.cs create mode 100644 ModuleManagerTests/Patches/PassSpecifiers/FirstPassSpecifierTest.cs create mode 100644 ModuleManagerTests/Patches/PassSpecifiers/ForPassSpecifierTest.cs create mode 100644 ModuleManagerTests/Patches/PassSpecifiers/InsertPassSpecifierTest.cs create mode 100644 ModuleManagerTests/Patches/PassSpecifiers/LegacyPassSpecifierTest.cs create mode 100644 ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 7c17bd27..6af7d5d7 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -15,7 +15,9 @@ using ModuleManager.Logging; using ModuleManager.Extensions; using ModuleManager.Collections; +using ModuleManager.Tags; using ModuleManager.Threading; +using ModuleManager.Patches; using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; @@ -156,35 +158,24 @@ private IEnumerator ProcessPatch() LoadPhysicsConfig(); - #region Check Needs - - - - // Do filtering with NEEDS - status = "Checking NEEDS."; - logger.Info(status); - yield return null; - NeedsChecker.CheckNeeds(GameDatabase.Instance.root, mods, progress, logger); - - #endregion Check Needs - #region Sorting Patches - status = "Sorting patches"; + status = "Extracting patches"; logger.Info(status); yield return null; - // PatchList patchList = PatchExtractor.SortAndExtractPatches(GameDatabase.Instance.root, mods, progress); - - PatchList patchList = new PatchList(mods); - PatchExtractor extractor = new PatchExtractor(patchList, progress, logger); + UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); + INeedsChecker needsChecker = new NeedsChecker(mods, gameData, progress, logger); + ITagListParser tagListParser = new TagListParser(); + IProtoPatchBuilder protoPatchBuilder = new ProtoPatchBuilder(progress); + IPatchCompiler patchCompiler = new PatchCompiler(); + PatchExtractor extractor = new PatchExtractor(progress, logger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler); // Have to convert to an array because we will be removing patches - foreach (UrlDir.UrlConfig urlConfig in GameDatabase.Instance.root.AllConfigs.ToArray()) - { - extractor.ExtractPatch(urlConfig); - } + UrlDir.UrlConfig[] allConfigs = GameDatabase.Instance.root.AllConfigs.ToArray(); + IEnumerable extractedPatches = allConfigs.Select(urlConfig => extractor.ExtractPatch(urlConfig)); + PatchList patchList = new PatchList(mods, extractedPatches.Where(patch => patch != null), progress); #endregion @@ -198,11 +189,14 @@ private IEnumerator ProcessPatch() MessageQueue logQueue = new MessageQueue(); IBasicLogger patchLogger = new QueueLogger(logQueue); IPatchProgress threadPatchProgress = new PatchProgress(progress, patchLogger); - PatchApplier applier = new PatchApplier(patchList, GameDatabase.Instance.root, threadPatchProgress, patchLogger); + PatchApplier applier = new PatchApplier(threadPatchProgress, patchLogger); logger.Info("Starting patch thread"); - ITaskStatus patchThread = BackgroundTask.Start(applier.ApplyPatches); + ITaskStatus patchThread = BackgroundTask.Start(delegate + { + applier.ApplyPatches(GameDatabase.Instance.root.AllConfigFiles.ToArray(), patchList); + }); float nextYield = Time.realtimeSinceStartup + yieldInterval; diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 3468f389..57fc26ae 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -63,11 +63,21 @@ + + + + + + + + + + diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs index 792766cf..7e452642 100644 --- a/ModuleManager/NeedsChecker.cs +++ b/ModuleManager/NeedsChecker.cs @@ -8,87 +8,122 @@ namespace ModuleManager { - public static class NeedsChecker + public interface INeedsChecker { - public static void CheckNeeds(UrlDir gameDatabaseRoot, IEnumerable mods, IPatchProgress progress, IBasicLogger logger) + bool CheckNeeds(string mod); + bool CheckNeedsExpression(string needsString); + void CheckNeedsRecursive(ConfigNode node, UrlDir.UrlConfig urlConfig); + } + + public class NeedsChecker : INeedsChecker + { + private readonly IEnumerable mods; + private readonly UrlDir gameData; + private readonly IPatchProgress progress; + private readonly IBasicLogger logger; + + public NeedsChecker(IEnumerable mods, UrlDir gameData, IPatchProgress progress, IBasicLogger logger) { - UrlDir gameData = gameDatabaseRoot.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); + this.mods = mods ?? throw new ArgumentNullException(nameof(mods)); + this.gameData = gameData ?? throw new ArgumentNullException(nameof(gameData)); + this.progress = progress ?? throw new ArgumentNullException(nameof(progress)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } - foreach (UrlDir.UrlConfig mod in gameDatabaseRoot.AllConfigs.ToArray()) + public bool CheckNeeds(string mod) + { + if (mod == null) throw new ArgumentNullException(nameof(mod)); + if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); + return mods.Contains(mod, StringComparer.InvariantCultureIgnoreCase); + } + + public bool CheckNeedsExpression(string needsExpression) + { + if (needsExpression == null) throw new ArgumentNullException(nameof(needsExpression)); + if (needsExpression == string.Empty) throw new ArgumentException("can't be empty", nameof(needsExpression)); + + foreach (string andDependencies in needsExpression.Split(',', '&')) { - UrlDir.UrlConfig currentMod = mod; - try + bool orMatch = false; + foreach (string orDependency in andDependencies.Split('|')) { - if (mod.config.name == null) - { - progress.Error(currentMod, "Error - Node in file " + currentMod.parent.url + " subnode: " + currentMod.type + - " has config.name == null"); - } + if (orDependency.Length == 0) + continue; - UrlDir.UrlConfig newMod; + bool not = orDependency[0] == '!'; + string toFind = not ? orDependency.Substring(1) : orDependency; - if (currentMod.type.IndexOf(":NEEDS[", StringComparison.OrdinalIgnoreCase) >= 0) - { - string type = currentMod.type; - - if (CheckNeeds(ref type, mods, gameData)) - { - - ConfigNode copy = new ConfigNode(type); - copy.ShallowCopyFrom(currentMod.config); - int index = mod.parent.configs.IndexOf(currentMod); - newMod = new UrlDir.UrlConfig(currentMod.parent, copy); - mod.parent.configs[index] = newMod; - } - else - { - progress.NeedsUnsatisfiedRoot(currentMod); - mod.parent.configs.Remove(currentMod); - continue; - } - } - else - { - newMod = currentMod; - } + bool found = CheckNeedsWithDirectories(toFind); - // Recursively check the contents - PatchContext context = new PatchContext(newMod, gameDatabaseRoot, logger, progress); - CheckNeeds(new NodeStack(newMod.config), context, mods, gameData); - } - catch (Exception ex) - { - try - { - mod.parent.configs.Remove(currentMod); - } - catch(Exception ex2) + if (not == !found) { - logger.Exception("Exception while attempting to ensure config removed" ,ex2); + orMatch = true; + break; } + } + if (!orMatch) + return false; + } - try - { - progress.Exception(mod, "Exception while checking needs on root node :\n" + mod.PrettyPrint(), ex); - } - catch (Exception ex2) + return true; + } + + public void CheckNeedsRecursive(ConfigNode node, UrlDir.UrlConfig urlConfig) + { + if (node == null) throw new ArgumentNullException(nameof(node)); + if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig)); + CheckNeedsRecursive(new NodeStack(node), urlConfig); + } + + private bool CheckNeedsWithDirectories(string mod) + { + if (CheckNeeds(mod)) return true; + if (mod.Contains('/')) + { + string[] splits = mod.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + bool result = true; + UrlDir current = gameData; + for (int i = 0; i < splits.Length; i++) + { + current = current.children.FirstOrDefault(dir => dir.name == splits[i]); + if (current == null) { - progress.Exception("Exception while attempting to log an exception", ex2); + result = false; + break; } } + return result; } + return false; + } + + private bool CheckNeedsName(ref string name) + { + if (name == null) + return true; + + int idxStart = name.IndexOf(":NEEDS[", StringComparison.OrdinalIgnoreCase); + if (idxStart < 0) + return true; + int idxEnd = name.IndexOf(']', idxStart + 7); + string needsString = name.Substring(idxStart + 7, idxEnd - idxStart - 7); + + name = name.Substring(0, idxStart) + name.Substring(idxEnd + 1); + + return CheckNeedsExpression(needsString); } - private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerable mods, UrlDir gameData) + private void CheckNeedsRecursive(NodeStack nodeStack, UrlDir.UrlConfig urlConfig) { - ConfigNode original = stack.value; + ConfigNode original = nodeStack.value; for (int i = 0; i < original.values.Count; ++i) { ConfigNode.Value val = original.values[i]; string valname = val.name; try { - if (CheckNeeds(ref valname, mods, gameData)) + if (CheckNeedsName(ref valname)) { val.name = valname; } @@ -96,17 +131,17 @@ private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerabl { original.values.Remove(val); i--; - context.progress.NeedsUnsatisfiedValue(context.patchUrl, stack, val.name); + progress.NeedsUnsatisfiedValue(urlConfig, nodeStack.GetPath() + '/' + val.name); } } catch (ArgumentOutOfRangeException e) { - context.progress.Exception("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"", e); + progress.Exception("ArgumentOutOfRangeException in CheckNeeds for value \"" + val.name + "\"", e); throw; } catch (Exception e) { - context.progress.Exception("General Exception in CheckNeeds for value \"" + val.name + "\"", e); + progress.Exception("General Exception in CheckNeeds for value \"" + val.name + "\"", e); throw; } } @@ -118,94 +153,34 @@ private static void CheckNeeds(NodeStack stack, PatchContext context, IEnumerabl if (nodeName == null) { - context.progress.Error(context.patchUrl, "Error - Node in file " + context.patchUrl.SafeUrl() + " subnode: " + stack.GetPath() + - " has config.name == null"); + progress.Error(urlConfig, "Error - Node in file " + urlConfig.SafeUrl() + " subnode: " + nodeStack.GetPath() + " has config.name == null"); } try { - if (CheckNeeds(ref nodeName, mods, gameData)) + if (CheckNeedsName(ref nodeName)) { node.name = nodeName; - CheckNeeds(stack.Push(node), context, mods, gameData); + CheckNeedsRecursive(nodeStack.Push(node), urlConfig); } else { original.nodes.Remove(node); i--; - context.progress.NeedsUnsatisfiedNode(context.patchUrl, stack.Push(node)); + progress.NeedsUnsatisfiedNode(urlConfig, nodeStack.GetPath() + '/' + node.name); } } catch (ArgumentOutOfRangeException e) { - context.progress.Exception("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"", e); + progress.Exception("ArgumentOutOfRangeException in CheckNeeds for node \"" + node.name + "\"", e); throw; } catch (Exception e) { - context.progress.Exception("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"", e); + progress.Exception("General Exception " + e.GetType().Name + " for node \"" + node.name + "\"", e); throw; } } } - - /// - /// Returns true if needs are satisfied. - /// - private static bool CheckNeeds(ref string name, IEnumerable mods, UrlDir gameData) - { - if (name == null) - return true; - - int idxStart = name.IndexOf(":NEEDS[", StringComparison.OrdinalIgnoreCase); - if (idxStart < 0) - return true; - int idxEnd = name.IndexOf(']', idxStart + 7); - string needsString = name.Substring(idxStart + 7, idxEnd - idxStart - 7); - - name = name.Substring(0, idxStart) + name.Substring(idxEnd + 1); - - // Check to see if all the needed dependencies are present. - foreach (string andDependencies in needsString.Split(',', '&')) - { - bool orMatch = false; - foreach (string orDependency in andDependencies.Split('|')) - { - if (orDependency.Length == 0) - continue; - - bool not = orDependency[0] == '!'; - string toFind = not ? orDependency.Substring(1) : orDependency; - - bool found = mods.Contains(toFind.ToUpper(), StringComparer.OrdinalIgnoreCase); - if (!found && toFind.Contains('/')) - { - string[] splits = toFind.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - - found = true; - UrlDir current = gameData; - for (int i = 0; i < splits.Length; i++) - { - current = current.children.FirstOrDefault(dir => dir.name == splits[i]); - if (current == null) - { - found = false; - break; - } - } - } - - if (not == !found) - { - orMatch = true; - break; - } - } - if (!orMatch) - return false; - } - - return true; - } } } diff --git a/ModuleManager/NodeMatcher.cs b/ModuleManager/NodeMatcher.cs index cef87865..5c3a59ca 100644 --- a/ModuleManager/NodeMatcher.cs +++ b/ModuleManager/NodeMatcher.cs @@ -16,47 +16,19 @@ public class NodeMatcher : INodeMatcher private string[] namePatterns = null; private string constraints = ""; - public NodeMatcher(string nodeName) + public NodeMatcher(string type, string name, string constraints) { - if (nodeName == null) throw new ArgumentNullException(nameof(nodeName)); - if (nodeName == "") throw new ArgumentException("can't be empty", nameof(nodeName)); - if (!nodeName.IsBracketBalanced()) throw new FormatException("node name is not bracket balanced: " + nodeName); - string name = nodeName; + if (type == string.Empty) throw new ArgumentException("can't be empty", nameof(type)); + this.type = type ?? throw new ArgumentNullException(nameof(type)); - int indexOfHas = name.IndexOf(":HAS[", StringComparison.InvariantCultureIgnoreCase); - - if (indexOfHas == 0) - { - throw new FormatException("node name cannot begin with :HAS : " + nodeName); - } - else if (indexOfHas > 0) - { - int closingBracketIndex = name.LastIndexOf(']', name.Length - 1, name.Length - indexOfHas - 1); - // Really shouldn't happen if we're bracket balanced but just in case - if (closingBracketIndex == -1) throw new FormatException("Malformed :HAS[] block detected: " + nodeName); - - constraints = name.Substring(indexOfHas + 5, closingBracketIndex - indexOfHas - 5); - name = name.Substring(0, indexOfHas); - } + if (name == string.Empty) throw new ArgumentException("can't be empty (null allowed)", nameof(name)); + if (constraints == string.Empty) throw new ArgumentException("can't be empty (null allowed)", nameof(constraints)); - int bracketIndex = name.IndexOf('['); - if (bracketIndex == 0) - { - throw new FormatException("node name cannot begin with a bracket: " + nodeName); - } - else if (bracketIndex > 0) - { - int closingBracketIndex = name.LastIndexOf(']', name.Length - 1, name.Length - bracketIndex - 1); - // Really shouldn't happen if we're bracket balanced but just in case - if (closingBracketIndex == -1) throw new FormatException("Malformed brackets detected: " + nodeName); - string patterns = name.Substring(bracketIndex + 1, closingBracketIndex - bracketIndex - 1); - namePatterns = patterns.Split(',', '|'); - type = name.Substring(0, bracketIndex); - } - else + if (name != null) namePatterns = name.Split(',', '|'); + if (constraints != null) { - type = name; - namePatterns = null; + if (!constraints.IsBracketBalanced()) throw new ArgumentException("is not bracket balanced: " + constraints, nameof(constraints)); + this.constraints = constraints; } } diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index f98b7c49..83440d70 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using ModuleManager.Logging; using ModuleManager.Extensions; @@ -11,31 +12,26 @@ public class PatchApplier { private readonly IBasicLogger logger; private readonly IPatchProgress progress; - - private readonly IPatchList patchList; - - private readonly UrlDir.UrlFile[] allConfigFiles; public string Activity { get; private set; } - public PatchApplier(IPatchList patchList, UrlDir databaseRoot, IPatchProgress progress, IBasicLogger logger) + public PatchApplier(IPatchProgress progress, IBasicLogger logger) { - this.patchList = patchList; - this.progress = progress; - this.logger = logger; - - allConfigFiles = databaseRoot.AllConfigFiles.ToArray(); + this.progress = progress ?? throw new ArgumentNullException(nameof(progress)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public void ApplyPatches() + public void ApplyPatches(IEnumerable configFiles, IEnumerable patches) { - foreach (IPass pass in patchList) + if (configFiles == null) throw new ArgumentNullException(nameof(configFiles)); + if (patches == null) throw new ArgumentNullException(nameof(patches)); + foreach (IPass pass in patches) { - ApplyPatches(pass); + ApplyPatches(configFiles, pass); } } - private void ApplyPatches(IPass pass) + private void ApplyPatches(IEnumerable configFiles, IPass pass) { logger.Info(pass.Name + " pass"); Activity = "ModuleManager " + pass.Name; @@ -44,7 +40,7 @@ private void ApplyPatches(IPass pass) { try { - foreach (UrlDir.UrlFile file in allConfigFiles) + foreach (UrlDir.UrlFile file in configFiles) { patch.Apply(file, progress, logger); } @@ -53,15 +49,7 @@ private void ApplyPatches(IPass pass) catch (Exception e) { progress.Exception(patch.UrlConfig, "Exception while processing node : " + patch.UrlConfig.SafeUrl(), e); - - try - { - logger.Error("Processed node was\n" + patch.UrlConfig.PrettyPrint()); - } - catch (Exception ex2) - { - logger.Exception("Exception while attempting to print a node", ex2); - } + logger.Error("Processed node was\n" + patch.UrlConfig.PrettyPrint()); } } } diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 06de7182..6c604576 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -1,212 +1,129 @@ using System; -using System.Text.RegularExpressions; using ModuleManager.Extensions; using ModuleManager.Logging; using ModuleManager.Patches; using ModuleManager.Progress; +using ModuleManager.Tags; namespace ModuleManager { public class PatchExtractor { - private static readonly Regex firstRegex = new Regex(@":FIRST", RegexOptions.IgnoreCase); - private static readonly Regex finalRegex = new Regex(@":FINAL", RegexOptions.IgnoreCase); - private static readonly Regex beforeRegex = new Regex(@":BEFORE(?:\[([^\[\]]+)\])?", RegexOptions.IgnoreCase); - private static readonly Regex forRegex = new Regex(@":FOR(?:\[([^\[\]]+)\])?", RegexOptions.IgnoreCase); - private static readonly Regex afterRegex = new Regex(@":AFTER(?:\[([^\[\]]+)\])?", RegexOptions.IgnoreCase); - - private readonly IPatchList patchList; private readonly IPatchProgress progress; private readonly IBasicLogger logger; - - public PatchExtractor(IPatchList patchList, IPatchProgress progress, IBasicLogger logger) + private readonly INeedsChecker needsChecker; + private readonly ITagListParser tagListParser; + private readonly IProtoPatchBuilder protoPatchBuilder; + private readonly IPatchCompiler patchCompiler; + + public PatchExtractor( + IPatchProgress progress, + IBasicLogger logger, + INeedsChecker needsChecker, + ITagListParser tagListParser, + IProtoPatchBuilder protoPatchBuilder, + IPatchCompiler patchCompiler + ) { - this.patchList = patchList ?? throw new ArgumentNullException(nameof(patchList)); this.progress = progress ?? throw new ArgumentNullException(nameof(progress)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.needsChecker = needsChecker ?? throw new ArgumentNullException(nameof(needsChecker)); + this.tagListParser = tagListParser ?? throw new ArgumentNullException(nameof(tagListParser)); + this.protoPatchBuilder = protoPatchBuilder ?? throw new ArgumentNullException(nameof(protoPatchBuilder)); + this.patchCompiler = patchCompiler ?? throw new ArgumentNullException(nameof(patchCompiler)); } - public void ExtractPatch(UrlDir.UrlConfig urlConfig) + public IPatch ExtractPatch(UrlDir.UrlConfig urlConfig) { + if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig)); + try { + int index = urlConfig.parent.configs.IndexOf(urlConfig); + urlConfig.parent.configs.RemoveAt(index); + if (!urlConfig.type.IsBracketBalanced()) { progress.Error(urlConfig, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\n" + urlConfig.SafeUrl()); - urlConfig.parent.configs.Remove(urlConfig); - return; + return null; } Command command = CommandParser.Parse(urlConfig.type, out string name); - - Match firstMatch = firstRegex.Match(name); - Match finalMatch = finalRegex.Match(name); - Match beforeMatch = beforeRegex.Match(name); - Match forMatch = forRegex.Match(name); - Match afterMatch = afterRegex.Match(name); - - int matchCount = 0; - - if (firstMatch.Success) matchCount++; - if (finalMatch.Success) matchCount++; - if (beforeMatch.Success) matchCount++; - if (forMatch.Success) matchCount++; - if (afterMatch.Success) matchCount++; - - if (firstMatch.NextMatch().Success) matchCount++; - if (finalMatch.NextMatch().Success) matchCount++; - if (beforeMatch.NextMatch().Success) matchCount++; - if (forMatch.NextMatch().Success) matchCount++; - if (afterMatch.NextMatch().Success) matchCount++; - - bool error = false; - - if (command == Command.Insert && matchCount > 0) - { - progress.Error(urlConfig, $"Error - pass specifier detected on an insert node (not a patch): {urlConfig.SafeUrl()}"); - error = true; - } - else if (command == Command.Replace) + + if (command == Command.Replace) { progress.Error(urlConfig, $"Error - replace command (%) is not valid on a root node: {urlConfig.SafeUrl()}"); - error = true; + return null; } else if (command == Command.Create) { progress.Error(urlConfig, $"Error - create command (&) is not valid on a root node: {urlConfig.SafeUrl()}"); - error = true; + return null; } else if (command == Command.Rename) { progress.Error(urlConfig, $"Error - rename command (|) is not valid on a root node: {urlConfig.SafeUrl()}"); - error = true; + return null; } else if (command == Command.Paste) { progress.Error(urlConfig, $"Error - paste command (#) is not valid on a root node: {urlConfig.SafeUrl()}"); - error = true; + return null; } else if (command == Command.Special) { progress.Error(urlConfig, $"Error - special command (*) is not valid on a root node: {urlConfig.SafeUrl()}"); - error = true; + return null; } - if (matchCount > 1) - { - progress.Error(urlConfig, $"Error - more than one pass specifier on a node: {urlConfig.SafeUrl()}"); - error = true; - } - if (beforeMatch.Success && !beforeMatch.Groups[1].Success) - { - progress.Error(urlConfig, "Error - malformed :BEFORE patch specifier detected: " + urlConfig.SafeUrl()); - error = true; - } - if (forMatch.Success && !forMatch.Groups[1].Success) - { - progress.Error(urlConfig, "Error - malformed :FOR patch specifier detected: " + urlConfig.SafeUrl()); - error = true; - } - if (afterMatch.Success && !afterMatch.Groups[1].Success) + ITagList tagList; + try { - progress.Error(urlConfig, "Error - malformed :AFTER patch specifier detected: " + urlConfig.SafeUrl()); - error = true; + tagList = tagListParser.Parse(name); } - if (error) + catch (FormatException ex) { - urlConfig.parent.configs.Remove(urlConfig); - return; + progress.Error(urlConfig, $"Cannot parse node name as tag list: {ex.Message}\non: {urlConfig.SafeUrl()}"); + return null; } - if (command == Command.Insert) return; - - urlConfig.parent.configs.Remove(urlConfig); - - Match theMatch = null; - Action addPatch = null; + ProtoPatch protoPatch = protoPatchBuilder.Build(urlConfig, command, tagList); - if (firstMatch.Success) - { - theMatch = firstMatch; - addPatch = patchList.AddFirstPatch; - } - else if (finalMatch.Success) + if (protoPatch == null) { - theMatch = finalMatch; - addPatch = patchList.AddFinalPatch; + return null; } - else if (beforeMatch.Success) + + if (protoPatch.needs != null && !needsChecker.CheckNeedsExpression(protoPatch.needs)) { - if (CheckMod(beforeMatch, patchList, out string theMod)) - { - theMatch = beforeMatch; - addPatch = p => patchList.AddBeforePatch(theMod, p); - } - else - { - progress.NeedsUnsatisfiedBefore(urlConfig); - return; - } + progress.NeedsUnsatisfiedRoot(urlConfig); + return null; } - else if (forMatch.Success) + else if (!protoPatch.passSpecifier.CheckNeeds(needsChecker, progress)) { - if (CheckMod(forMatch, patchList, out string theMod)) - { - theMatch = forMatch; - addPatch = p => patchList.AddForPatch(theMod, p); - } - else - { - progress.NeedsUnsatisfiedFor(urlConfig); - return; - } + return null; } - else if (afterMatch.Success) + + if (command == Command.Insert) { - if (CheckMod(afterMatch, patchList, out string theMod)) - { - theMatch = afterMatch; - addPatch = p => patchList.AddAfterPatch(theMod, p); - } - else - { - progress.NeedsUnsatisfiedAfter(urlConfig); - return; - } + ConfigNode newNode = urlConfig.config.DeepCopy(); + newNode.name = protoPatch.nodeType; + newNode.id = urlConfig.config.id; + needsChecker.CheckNeedsRecursive(newNode, urlConfig); + urlConfig.parent.configs.Insert(index, new UrlDir.UrlConfig(urlConfig.parent, newNode)); + return null; } else { - addPatch = patchList.AddLegacyPatch; + needsChecker.CheckNeedsRecursive(urlConfig.config, urlConfig); + return patchCompiler.CompilePatch(protoPatch); } - - string newName; - if (theMatch == null) - newName = name; - else - newName = name.Remove(theMatch.Index, theMatch.Length); - - addPatch(PatchCompiler.CompilePatch(urlConfig, command, newName)); - progress.PatchAdded(); } catch(Exception e) { - progress.Exception(urlConfig, $"Exception while parsing pass for config: {urlConfig.SafeUrl()}", e); - - try - { - urlConfig.parent.configs.Remove(urlConfig); - } - catch (Exception ex) - { - logger.Exception("Exception while attempting to clean up bad config", ex); - } + progress.Exception(urlConfig, $"Exception while attempting to create patch from config: {urlConfig.SafeUrl()}", e); + return null; } } - - private static bool CheckMod(Match match, IPatchList patchList, out string theMod) - { - theMod = match.Groups[1].Value.Trim().ToLower(); - return patchList.HasMod(theMod); - } } } diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index e79f9570..6c44d0b3 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -4,21 +4,12 @@ using System.Linq; using ModuleManager.Collections; using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; namespace ModuleManager { - public interface IPatchList : IEnumerable - { - bool HasMod(string mod); - void AddFirstPatch(IPatch patch); - void AddLegacyPatch(IPatch patch); - void AddBeforePatch(string mod, IPatch patch); - void AddForPatch(string mod, IPatch patch); - void AddAfterPatch(string mod, IPatch patch); - void AddFinalPatch(IPatch patch); - } - - public class PatchList : IPatchList + public class PatchList : IEnumerable { private class ModPass { @@ -82,55 +73,54 @@ public ModPassCollection(IEnumerable modList) private readonly ModPassCollection modPasses; - public PatchList(IEnumerable modList) + public PatchList(IEnumerable modList, IEnumerable patches, IPatchProgress progress) { - modPasses = new ModPassCollection(modList); + modPasses = new ModPassCollection(modList ?? throw new ArgumentNullException(nameof(modList))); + if (patches == null) throw new ArgumentNullException(nameof(patches)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + + foreach (IPatch patch in patches) + { + if (patch.PassSpecifier is FirstPassSpecifier) + { + firstPatches.Add(patch); + } + else if (patch.PassSpecifier is LegacyPassSpecifier) + { + legacyPatches.Add(patch); + } + else if (patch.PassSpecifier is BeforePassSpecifier beforePassSpecifier) + { + EnsureMod(beforePassSpecifier.mod); + modPasses[beforePassSpecifier.mod].AddBeforePatch(patch); + } + else if (patch.PassSpecifier is ForPassSpecifier forPassSpecifier) + { + EnsureMod(forPassSpecifier.mod); + modPasses[forPassSpecifier.mod].AddForPatch(patch); + } + else if (patch.PassSpecifier is AfterPassSpecifier afterPassSpecifier) + { + EnsureMod(afterPassSpecifier.mod); + modPasses[afterPassSpecifier.mod].AddAfterPatch(patch); + } + else if (patch.PassSpecifier is FinalPassSpecifier) + { + finalPatches.Add(patch); + } + else + { + throw new NotImplementedException("Don't know what to do with pass specifier: " + patch.PassSpecifier.Descriptor); + } + + progress.PatchAdded(); + } } public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(EnumeratePasses()); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public bool HasMod(string mod) - { - if (mod == null) throw new ArgumentNullException(nameof(mod)); - if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); - return modPasses.HasMod(mod); - } - - public void AddFirstPatch(IPatch patch) - { - firstPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); - } - - public void AddLegacyPatch(IPatch patch) - { - legacyPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); - } - - public void AddBeforePatch(string mod, IPatch patch) - { - EnsureMod(mod); - modPasses[mod].AddBeforePatch(patch ?? throw new ArgumentNullException(nameof(patch))); - } - - public void AddForPatch(string mod, IPatch patch) - { - EnsureMod(mod); - modPasses[mod].AddForPatch(patch ?? throw new ArgumentNullException(nameof(patch))); - } - - public void AddAfterPatch(string mod, IPatch patch) - { - EnsureMod(mod); - modPasses[mod].AddAfterPatch(patch ?? throw new ArgumentNullException(nameof(patch))); - } - - public void AddFinalPatch(IPatch patch) - { - finalPatches.Add(patch ?? throw new ArgumentNullException(nameof(patch))); - } - private IPass[] EnumeratePasses() { IPass[] result = new IPass[modPasses.Count * 3 + 3]; @@ -154,7 +144,7 @@ private void EnsureMod(string mod) { if (mod == null) throw new ArgumentNullException(nameof(mod)); if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); - if (!HasMod(mod)) throw new KeyNotFoundException($"Mod '{mod}' not found"); + if (!modPasses.HasMod(mod)) throw new KeyNotFoundException($"Mod '{mod}' not found"); } } } diff --git a/ModuleManager/Patches/CopyPatch.cs b/ModuleManager/Patches/CopyPatch.cs index e8fa295f..b94ee353 100644 --- a/ModuleManager/Patches/CopyPatch.cs +++ b/ModuleManager/Patches/CopyPatch.cs @@ -2,6 +2,7 @@ using NodeStack = ModuleManager.Collections.ImmutableStack; using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; namespace ModuleManager.Patches @@ -10,11 +11,13 @@ public class CopyPatch : IPatch { public UrlDir.UrlConfig UrlConfig { get; } public INodeMatcher NodeMatcher { get; } + public IPassSpecifier PassSpecifier { get; } - public CopyPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher) + public CopyPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier) { UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher)); + PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier)); } public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger) diff --git a/ModuleManager/Patches/DeletePatch.cs b/ModuleManager/Patches/DeletePatch.cs index 2fbcd879..6d8762f9 100644 --- a/ModuleManager/Patches/DeletePatch.cs +++ b/ModuleManager/Patches/DeletePatch.cs @@ -1,6 +1,7 @@ using System; using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; namespace ModuleManager.Patches @@ -9,11 +10,13 @@ public class DeletePatch : IPatch { public UrlDir.UrlConfig UrlConfig { get; } public INodeMatcher NodeMatcher { get; } + public IPassSpecifier PassSpecifier { get; } - public DeletePatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher) + public DeletePatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier) { UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher)); + PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier)); } public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger) diff --git a/ModuleManager/Patches/EditPatch.cs b/ModuleManager/Patches/EditPatch.cs index 4dfcc22f..b9358fdb 100644 --- a/ModuleManager/Patches/EditPatch.cs +++ b/ModuleManager/Patches/EditPatch.cs @@ -2,6 +2,7 @@ using NodeStack = ModuleManager.Collections.ImmutableStack; using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; namespace ModuleManager.Patches @@ -12,11 +13,13 @@ public class EditPatch : IPatch public UrlDir.UrlConfig UrlConfig { get; } public INodeMatcher NodeMatcher { get; } + public IPassSpecifier PassSpecifier { get; } - public EditPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher) + public EditPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier) { UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); NodeMatcher = nodeMatcher ?? throw new ArgumentNullException(nameof(nodeMatcher)); + PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier)); loop = urlConfig.config.HasNode("MM_PATCH_LOOP"); } diff --git a/ModuleManager/Patches/IPatch.cs b/ModuleManager/Patches/IPatch.cs index 57ae2cfa..57920050 100644 --- a/ModuleManager/Patches/IPatch.cs +++ b/ModuleManager/Patches/IPatch.cs @@ -1,5 +1,6 @@ using System; using ModuleManager.Logging; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; namespace ModuleManager.Patches @@ -8,6 +9,7 @@ public interface IPatch { UrlDir.UrlConfig UrlConfig { get; } INodeMatcher NodeMatcher { get; } + IPassSpecifier PassSpecifier { get; } void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger); } } diff --git a/ModuleManager/Patches/PassSpecifiers/AfterPassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/AfterPassSpecifier.cs new file mode 100644 index 00000000..a50b99c5 --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/AfterPassSpecifier.cs @@ -0,0 +1,29 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public class AfterPassSpecifier : IPassSpecifier + { + public readonly string mod; + public readonly UrlDir.UrlConfig urlConfig; + + public AfterPassSpecifier(string mod, UrlDir.UrlConfig urlConfig) + { + if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); + this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); + this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); + } + + public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) + { + if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + bool result = needsChecker.CheckNeeds(mod); + if (!result) progress.NeedsUnsatisfiedAfter(urlConfig); + return result; + } + + public string Descriptor => $":AFTER[{mod.ToUpper()}]"; + } +} diff --git a/ModuleManager/Patches/PassSpecifiers/BeforePassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/BeforePassSpecifier.cs new file mode 100644 index 00000000..9f509f43 --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/BeforePassSpecifier.cs @@ -0,0 +1,29 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public class BeforePassSpecifier : IPassSpecifier + { + public readonly string mod; + public readonly UrlDir.UrlConfig urlConfig; + + public BeforePassSpecifier(string mod, UrlDir.UrlConfig urlConfig) + { + if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); + this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); + this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); + } + + public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) + { + if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + bool result = needsChecker.CheckNeeds(mod); + if (!result) progress.NeedsUnsatisfiedBefore(urlConfig); + return result; + } + + public string Descriptor => $":BEFORE[{mod.ToUpper()}]"; + } +} diff --git a/ModuleManager/Patches/PassSpecifiers/FinalPassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/FinalPassSpecifier.cs new file mode 100644 index 00000000..7567f344 --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/FinalPassSpecifier.cs @@ -0,0 +1,17 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public class FinalPassSpecifier : IPassSpecifier + { + public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) + { + if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + return true; + } + + public string Descriptor => ":FINAL"; + } +} diff --git a/ModuleManager/Patches/PassSpecifiers/FirstPassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/FirstPassSpecifier.cs new file mode 100644 index 00000000..7d32bbbd --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/FirstPassSpecifier.cs @@ -0,0 +1,17 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public class FirstPassSpecifier : IPassSpecifier + { + public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) + { + if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + return true; + } + + public string Descriptor => ":FIRST"; + } +} diff --git a/ModuleManager/Patches/PassSpecifiers/ForPassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/ForPassSpecifier.cs new file mode 100644 index 00000000..3256a072 --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/ForPassSpecifier.cs @@ -0,0 +1,29 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public class ForPassSpecifier : IPassSpecifier + { + public readonly string mod; + public readonly UrlDir.UrlConfig urlConfig; + + public ForPassSpecifier(string mod, UrlDir.UrlConfig urlConfig) + { + if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); + this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); + this.urlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); + } + + public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) + { + if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + bool result = needsChecker.CheckNeeds(mod); + if (!result) progress.NeedsUnsatisfiedFor(urlConfig); + return result; + } + + public string Descriptor => $":FOR[{mod.ToUpper()}]"; + } +} diff --git a/ModuleManager/Patches/PassSpecifiers/IPassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/IPassSpecifier.cs new file mode 100644 index 00000000..384e52d5 --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/IPassSpecifier.cs @@ -0,0 +1,11 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public interface IPassSpecifier + { + bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress); + string Descriptor { get; } + } +} diff --git a/ModuleManager/Patches/PassSpecifiers/InsertPassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/InsertPassSpecifier.cs new file mode 100644 index 00000000..244f9c5c --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/InsertPassSpecifier.cs @@ -0,0 +1,17 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public class InsertPassSpecifier : IPassSpecifier + { + public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) + { + if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + return true; + } + + public string Descriptor => ":INSERT (initial)"; + } +} diff --git a/ModuleManager/Patches/PassSpecifiers/LegacyPassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/LegacyPassSpecifier.cs new file mode 100644 index 00000000..ddbee929 --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/LegacyPassSpecifier.cs @@ -0,0 +1,17 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public class LegacyPassSpecifier : IPassSpecifier + { + public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) + { + if (needsChecker == null) throw new ArgumentNullException(nameof(needsChecker)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + return true; + } + + public string Descriptor => ":LEGACY (default)"; + } +} diff --git a/ModuleManager/Patches/PatchCompiler.cs b/ModuleManager/Patches/PatchCompiler.cs index 3e71a3ce..11ce994b 100644 --- a/ModuleManager/Patches/PatchCompiler.cs +++ b/ModuleManager/Patches/PatchCompiler.cs @@ -2,29 +2,32 @@ namespace ModuleManager.Patches { - public static class PatchCompiler + public interface IPatchCompiler { - public static IPatch CompilePatch(UrlDir.UrlConfig urlConfig, Command command, string name) + IPatch CompilePatch(ProtoPatch protoPatch); + } + + public class PatchCompiler : IPatchCompiler + { + public IPatch CompilePatch(ProtoPatch protoPatch) { - if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (name == string.Empty) throw new ArgumentException("can't be empty", nameof(name)); + if (protoPatch == null) throw new ArgumentNullException(nameof(protoPatch)); - INodeMatcher nodeMatcher = new NodeMatcher(name); + INodeMatcher nodeMatcher = new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has); - switch (command) + switch (protoPatch.command) { case Command.Edit: - return new EditPatch(urlConfig, nodeMatcher); + return new EditPatch(protoPatch.urlConfig, nodeMatcher, protoPatch.passSpecifier); case Command.Copy: - return new CopyPatch(urlConfig, nodeMatcher); + return new CopyPatch(protoPatch.urlConfig, nodeMatcher, protoPatch.passSpecifier); case Command.Delete: - return new DeletePatch(urlConfig, nodeMatcher); + return new DeletePatch(protoPatch.urlConfig, nodeMatcher, protoPatch.passSpecifier); default: - throw new ArgumentException("invalid command for a root node: " + command, nameof(command)); + throw new ArgumentException("has an invalid command for a root node: " + protoPatch.command, nameof(protoPatch)); } } } diff --git a/ModuleManager/Patches/ProtoPatch.cs b/ModuleManager/Patches/ProtoPatch.cs new file mode 100644 index 00000000..c1ad8572 --- /dev/null +++ b/ModuleManager/Patches/ProtoPatch.cs @@ -0,0 +1,27 @@ +using System; +using ModuleManager.Patches.PassSpecifiers; + +namespace ModuleManager.Patches +{ + public class ProtoPatch + { + public readonly UrlDir.UrlConfig urlConfig; + public readonly Command command; + public readonly string nodeType; + public readonly string nodeName; + public readonly string needs = null; + public readonly string has = null; + public readonly IPassSpecifier passSpecifier; + + public ProtoPatch(UrlDir.UrlConfig urlConfig, Command command, string nodeType, string nodeName, string needs, string has, IPassSpecifier passSpecifier) + { + this.urlConfig = urlConfig; + this.command = command; + this.nodeType = nodeType; + this.nodeName = nodeName; + this.needs = needs; + this.has = has; + this.passSpecifier = passSpecifier; + } + } +} diff --git a/ModuleManager/Patches/ProtoPatchBuilder.cs b/ModuleManager/Patches/ProtoPatchBuilder.cs new file mode 100644 index 00000000..7979aac3 --- /dev/null +++ b/ModuleManager/Patches/ProtoPatchBuilder.cs @@ -0,0 +1,224 @@ +using System; +using ModuleManager.Extensions; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; +using ModuleManager.Tags; + +namespace ModuleManager.Patches +{ + public interface IProtoPatchBuilder + { + ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList tagList); + } + + public class ProtoPatchBuilder : IProtoPatchBuilder + { + private readonly IPatchProgress progress; + + public ProtoPatchBuilder(IPatchProgress progress) + { + this.progress = progress ?? throw new ArgumentNullException(nameof(progress)); + } + + public ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList tagList) + { + if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig)); + if (tagList == null) throw new ArgumentNullException(nameof(tagList)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + + bool error = false; + + string nodeType = tagList.PrimaryTag.key; + string nodeName = tagList.PrimaryTag.value; + + if (command == Command.Insert && nodeName != null) + { + progress.Error(urlConfig, "name specifier detected on insert node (not a patch): " + urlConfig.SafeUrl()); + error = true; + } + + if (nodeName == string.Empty) + { + progress.Warning(urlConfig, "empty brackets detected on patch name: " + urlConfig.SafeUrl()); + nodeName = null; + } + + if (tagList.PrimaryTag.trailer != null) + progress.Warning(urlConfig, "unrecognized trailer: '" + tagList.PrimaryTag.trailer + "' on: " + urlConfig.SafeUrl()); + + string needs = null; + string has = null; + IPassSpecifier passSpecifier = null; + + foreach (Tag tag in tagList) + { + if (tag.trailer != null) + progress.Warning(urlConfig, "unrecognized trailer: '" + tag.trailer + "' on: " + urlConfig.SafeUrl()); + + if (tag.key.Equals("NEEDS", StringComparison.CurrentCultureIgnoreCase)) + { + if (needs != null) + { + progress.Warning(urlConfig, "more than one :NEEDS tag detected, ignoring all but the first: " + urlConfig.SafeUrl()); + continue; + } + if (string.IsNullOrEmpty(tag.value)) + { + progress.Error(urlConfig, "empty :NEEDS tag detected: " + urlConfig.SafeUrl()); + error = true; + continue; + } + + needs = tag.value; + } + else if (tag.key.Equals("HAS", StringComparison.CurrentCultureIgnoreCase)) + { + if (command == Command.Insert) + { + progress.Error(urlConfig, ":HAS detected on insert node (not a patch): " + urlConfig.SafeUrl()); + error = true; + continue; + } + if (has != null) + { + progress.Warning(urlConfig, "more than one :HAS tag detected, ignoring all but the first: " + urlConfig.SafeUrl()); + continue; + } + if (string.IsNullOrEmpty(tag.value)) + { + progress.Error(urlConfig, "empty :HAS tag detected: " + urlConfig.SafeUrl()); + error = true; + continue; + } + + has = tag.value; + } + else if (tag.key.Equals("FIRST", StringComparison.CurrentCultureIgnoreCase)) + { + if (tag.value != null) + { + progress.Warning(urlConfig, "value detected on :FIRST tag: " + urlConfig.SafeUrl()); + } + + if (command == Command.Insert) + { + progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl()); + error = true; + continue; + } + if (passSpecifier != null) + { + progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl()); + continue; + } + + passSpecifier = new FirstPassSpecifier(); + } + else if (tag.key.Equals("BEFORE", StringComparison.CurrentCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(tag.value)) + { + progress.Error(urlConfig, "empty :BEFORE tag detected: " + urlConfig.SafeUrl()); + error = true; + continue; + } + + if (command == Command.Insert) + { + progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl()); + error = true; + continue; + } + if (passSpecifier != null) + { + progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl()); + continue; + } + + passSpecifier = new BeforePassSpecifier(tag.value, urlConfig); + } + else if (tag.key.Equals("FOR", StringComparison.CurrentCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(tag.value)) + { + progress.Error(urlConfig, "empty :FOR tag detected: " + urlConfig.SafeUrl()); + error = true; + continue; + } + + if (command == Command.Insert) + { + progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl()); + error = true; + continue; + } + if (passSpecifier != null) + { + progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl()); + continue; + } + + passSpecifier = new ForPassSpecifier(tag.value, urlConfig); + } + else if (tag.key.Equals("AFTER", StringComparison.CurrentCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(tag.value)) + { + progress.Error(urlConfig, "empty :AFTER tag detected: " + urlConfig.SafeUrl()); + error = true; + continue; + } + + if (command == Command.Insert) + { + progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl()); + error = true; + continue; + } + if (passSpecifier != null) + { + progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl()); + continue; + } + + passSpecifier = new AfterPassSpecifier(tag.value, urlConfig); + } + else if (tag.key.Equals("FINAL", StringComparison.CurrentCultureIgnoreCase)) + { + if (tag.value != null) + { + progress.Warning(urlConfig, "value detected on :FINAL tag: " + urlConfig.SafeUrl()); + } + + if (command == Command.Insert) + { + progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl()); + error = true; + continue; + } + if (passSpecifier != null) + { + progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl()); + continue; + } + + passSpecifier = new FinalPassSpecifier(); + } + else + { + progress.Warning(urlConfig, "unrecognized tag: '" + tag.key + "' on: " + urlConfig.SafeUrl()); + } + } + + if (error) return null; + + if (passSpecifier == null) + { + if (command == Command.Insert) passSpecifier = new InsertPassSpecifier(); + else passSpecifier = new LegacyPassSpecifier(); + } + + return new ProtoPatch(urlConfig, command, nodeType, nodeName, needs, has, passSpecifier); + } + } +} diff --git a/ModuleManager/Progress/IPatchProgress.cs b/ModuleManager/Progress/IPatchProgress.cs index 3b6f13df..fe03995d 100644 --- a/ModuleManager/Progress/IPatchProgress.cs +++ b/ModuleManager/Progress/IPatchProgress.cs @@ -14,8 +14,8 @@ public interface IPatchProgress void Exception(string message, Exception exception); void Exception(UrlDir.UrlConfig url, string message, Exception exception); void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url); - void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, NodeStack path); - void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, NodeStack path, string valName); + void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, string path); + void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, string path); void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url); void NeedsUnsatisfiedFor(UrlDir.UrlConfig url); void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url); diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index 9f1f6cfa..701fa4ef 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -67,14 +67,14 @@ public void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url) Counter.needsUnsatisfied.Increment(); } - public void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, NodeStack path) + public void NeedsUnsatisfiedNode(UrlDir.UrlConfig url, string path) { - logger.Info($"Deleting node in file {url.parent.url} subnode: {path.GetPath()} as it can't satisfy its NEEDS"); + logger.Info($"Deleting node in file {url.parent.url} subnode: {path} as it can't satisfy its NEEDS"); } - public void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, NodeStack path, string valName) + public void NeedsUnsatisfiedValue(UrlDir.UrlConfig url, string path) { - logger.Info($"Deleting value in file {url.parent.url} subnode: {path.GetPath()} value: {valName} as it can't satisfy its NEEDS"); + logger.Info($"Deleting value in file {url.parent.url} value: {path} as it can't satisfy its NEEDS"); } public void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7a2d4e8c..6e1a83bf 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -64,10 +64,18 @@ + + + + + + + + diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index 73c2918b..d0414a0b 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -1,233 +1,217 @@ using System; -using System.Linq; using Xunit; using NSubstitute; using TestUtils; using ModuleManager; using ModuleManager.Logging; using ModuleManager.Progress; -using ModuleManager.Extensions; using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManagerTests { public class NeedsCheckerTest { - private readonly UrlDir root; private readonly UrlDir gameData; - private readonly UrlDir.UrlFile file; private readonly IPatchProgress progress; private readonly IBasicLogger logger; + private NeedsChecker needsChecker; public NeedsCheckerTest() { - root = UrlBuilder.CreateRoot(); - gameData = UrlBuilder.CreateGameData(root); - file = UrlBuilder.CreateFile("abc/def.cfg", gameData); + gameData = UrlBuilder.CreateGameData(); progress = Substitute.For(); logger = Substitute.For(); + + needsChecker = new NeedsChecker(new[] { "mod1", "mod2", "mod/2" }, gameData, progress, logger); } [Fact] - public void TestCheckNeeds__Root() + public void TestConstructor__ModsNull() { - string[] modList = { "mod1", "mod2" }; - - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1]"), file); - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:needs[mod1]"), file); - UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod2]:AFTER[mod3]"), file); - - UrlDir.UrlConfig config5 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]"), file); - UrlDir.UrlConfig config6 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:needs[mod3]"), file); - UrlDir.UrlConfig config7 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]:FOR[mod2]"), file); - - NeedsChecker.CheckNeeds(root, modList, progress, logger); - - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); + ArgumentNullException ex = Assert.Throws(delegate + { + new NeedsChecker(null, gameData, progress, logger); + }); - UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); - Assert.Equal(4, configs.Length); - - Assert.Same(config1, configs[0]); - AssertUrlCorrect("SOME_NODE", config2, configs[1]); - AssertUrlCorrect("SOME_NODE", config3, configs[2]); - AssertUrlCorrect("SOME_NODE:AFTER[mod3]", config4, configs[3]); - - progress.Received().NeedsUnsatisfiedRoot(config5); - progress.Received().NeedsUnsatisfiedRoot(config6); - progress.Received().NeedsUnsatisfiedRoot(config7); + Assert.Equal("mods", ex.ParamName); } [Fact] - public void TestCheckNeeds__Root__AndOr() + public void TestConstructor__GameDataNull() { - string[] modList = { "mod1", "mod2" }; - - UrlDir.UrlConfig noNeedsNode = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); - - UrlDir.UrlConfig[] needsSatisfiedConfigs = new[] { - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1&mod2]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,mod2]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod2]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1&mod2|mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,mod2|mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod3&mod1]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod,mod1]"), file), - }; - - UrlDir.UrlConfig[] needsUnsatisfiedConfigs = new[] { - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1&mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1&mod2&mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,mod2,mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3|mod4]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod2&mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|mod2,mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3&mod1|mod2]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3,mod1|mod2]"), file), - }; - - NeedsChecker.CheckNeeds(root, modList, progress, logger); + ArgumentNullException ex = Assert.Throws(delegate + { + new NeedsChecker(new string[0], null, progress, logger); + }); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); + Assert.Equal("gameData", ex.ParamName); + } - UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); - Assert.Equal(needsSatisfiedConfigs.Length + 1, configs.Length); + [Fact] + public void TestConstructor__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new NeedsChecker(new string[0], gameData, null, logger); + }); - Assert.Same(noNeedsNode, configs[0]); + Assert.Equal("progress", ex.ParamName); + } - for (int i = 0; i < needsSatisfiedConfigs.Length; i++) + [Fact] + public void TestConstructor__LoggerNull() + { + ArgumentNullException ex = Assert.Throws(delegate { - AssertUrlCorrect("SOME_NODE", needsSatisfiedConfigs[i], configs[i + 1]); - } + new NeedsChecker(new string[0], gameData, progress, null); + }); - foreach (UrlDir.UrlConfig config in needsUnsatisfiedConfigs) - { - progress.Received().NeedsUnsatisfiedRoot(config); - } + Assert.Equal("logger", ex.ParamName); } [Fact] - public void TestCheckNeeds__Root__Not() + public void TestCheckNeedsExpression() { - string[] modList = { "mod1", "mod2" }; - - UrlDir.UrlConfig noNeedsNode = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); + Assert.True(needsChecker.CheckNeedsExpression("mod1")); + Assert.True(needsChecker.CheckNeedsExpression("mod2")); + Assert.False(needsChecker.CheckNeedsExpression("mod3")); + } - UrlDir.UrlConfig[] needsSatisfiedConfigs = new[] { - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1,!mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod1|!mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1|!mod2]"), file), - }; + [Fact] + public void TestCheckNeedsExpression__AndOr() + { + Assert.True(needsChecker.CheckNeedsExpression("mod1&mod2")); + Assert.True(needsChecker.CheckNeedsExpression("mod1,mod2")); + Assert.True(needsChecker.CheckNeedsExpression("mod1|mod2")); + Assert.True(needsChecker.CheckNeedsExpression("mod1|mod3")); + Assert.True(needsChecker.CheckNeedsExpression("mod1&mod2|mod3")); + Assert.True(needsChecker.CheckNeedsExpression("mod1,mod2|mod3")); + Assert.True(needsChecker.CheckNeedsExpression("mod1|mod3&mod2")); + Assert.True(needsChecker.CheckNeedsExpression("mod1|mod3,mod2")); + + Assert.False(needsChecker.CheckNeedsExpression("mod1&mod3")); + Assert.False(needsChecker.CheckNeedsExpression("mod1,mod3")); + Assert.False(needsChecker.CheckNeedsExpression("mod1&mod2&mod3")); + Assert.False(needsChecker.CheckNeedsExpression("mod1,mod2,mod3")); + Assert.False(needsChecker.CheckNeedsExpression("mod3|mod4")); + Assert.False(needsChecker.CheckNeedsExpression("mod1|mod2&mod3")); + Assert.False(needsChecker.CheckNeedsExpression("mod1|mod2,mod3")); + Assert.False(needsChecker.CheckNeedsExpression("mod3&mod1|mod2")); + Assert.False(needsChecker.CheckNeedsExpression("mod3,mod1|mod2")); + } - UrlDir.UrlConfig[] needsUnsatisfiedConfigs = new[] { - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod1]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod1,mod2]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[!mod1&!mod3]"), file), - }; + [Fact] + public void TestCheckNeedsExpression__Not() + { + Assert.True(needsChecker.CheckNeedsExpression("!mod3")); + Assert.True(needsChecker.CheckNeedsExpression("mod1,!mod3")); + Assert.True(needsChecker.CheckNeedsExpression("!mod1|!mod3")); + Assert.True(needsChecker.CheckNeedsExpression("mod1|!mod2")); + + Assert.False(needsChecker.CheckNeedsExpression("!mod1")); + Assert.False(needsChecker.CheckNeedsExpression("!mod1,mod2")); + Assert.False(needsChecker.CheckNeedsExpression("!mod1&!mod3")); + } - NeedsChecker.CheckNeeds(root, modList, progress, logger); + [Fact] + public void TestCheckNeedsExpression__Capitalization() + { + Assert.True(needsChecker.CheckNeedsExpression("mod1")); + Assert.True(needsChecker.CheckNeedsExpression("Mod1")); + Assert.True(needsChecker.CheckNeedsExpression("MOD1")); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); + Assert.False(needsChecker.CheckNeedsExpression("mod3")); + Assert.False(needsChecker.CheckNeedsExpression("Mod3")); + Assert.False(needsChecker.CheckNeedsExpression("MOD3")); + } - UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); - Assert.Equal(needsSatisfiedConfigs.Length + 1, configs.Length); + [Fact] + public void TestCheckNeedsExpression__Directory() + { + UrlBuilder.CreateDir("abc", gameData); + UrlBuilder.CreateDir("ghi/jkl", gameData); - Assert.Same(noNeedsNode, configs[0]); + Assert.True(needsChecker.CheckNeedsExpression("/abc")); + Assert.True(needsChecker.CheckNeedsExpression("abc/")); + Assert.True(needsChecker.CheckNeedsExpression("/abc/")); + Assert.True(needsChecker.CheckNeedsExpression("ghi/jkl")); + Assert.True(needsChecker.CheckNeedsExpression("/ghi/jkl")); + Assert.True(needsChecker.CheckNeedsExpression("ghi/jkl/")); + Assert.True(needsChecker.CheckNeedsExpression("mod1&ghi/jkl")); + Assert.True(needsChecker.CheckNeedsExpression("mod3|ghi/jkl")); + Assert.True(needsChecker.CheckNeedsExpression("abc/&ghi/jkl")); + Assert.True(needsChecker.CheckNeedsExpression("mod/2")); + + Assert.False(needsChecker.CheckNeedsExpression("abc")); + Assert.False(needsChecker.CheckNeedsExpression("mod3&ghi/jkl")); + Assert.False(needsChecker.CheckNeedsExpression("Ghi/jkl")); + Assert.False(needsChecker.CheckNeedsExpression("mno/pqr")); + } - for (int i = 0; i < needsSatisfiedConfigs.Length; i++) + [Fact] + public void TestCheckNeedsExpression__Null() + { + ArgumentNullException ex = Assert.Throws(delegate { - AssertUrlCorrect("SOME_NODE", needsSatisfiedConfigs[i], configs[i + 1]); - } + needsChecker.CheckNeedsExpression(null); + }); - foreach (UrlDir.UrlConfig config in needsUnsatisfiedConfigs) - { - progress.Received().NeedsUnsatisfiedRoot(config); - } + Assert.Equal("needsExpression", ex.ParamName); } [Fact] - public void TestCheckNeeds__Root__CaseInsensitive() + public void TestCheckNeedsExpression__Empty() { - string[] modList = { "mod1", "mod2" }; - - UrlDir.UrlConfig noNeedsNode = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); - - UrlDir.UrlConfig[] needsSatisfiedConfigs = new[] { - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod1]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[Mod1]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[MOD1]"), file), - }; - - UrlDir.UrlConfig[] needsUnsatisfiedConfigs = new[] { - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[Mod3]"), file), - UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[MOD3]"), file), - }; + ArgumentException ex = Assert.Throws(delegate + { + needsChecker.CheckNeedsExpression(""); + }); - NeedsChecker.CheckNeeds(root, modList, progress, logger); + Assert.Equal("needsExpression", ex.ParamName); + Assert.Contains("can't be empty", ex.Message); + } - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); + [Fact] + public void TestCheckNeeds() + { + UrlBuilder.CreateDir("ghi/jkl", gameData); - UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); - Assert.Equal(needsSatisfiedConfigs.Length + 1, configs.Length); + Assert.True(needsChecker.CheckNeeds("mod1")); + Assert.True(needsChecker.CheckNeeds("MOD1")); + Assert.True(needsChecker.CheckNeeds("mod2")); - Assert.Same(noNeedsNode, configs[0]); + Assert.False(needsChecker.CheckNeeds("mod1&mod2")); + Assert.False(needsChecker.CheckNeeds("ghi/jkl")); + } - for (int i = 0; i < needsSatisfiedConfigs.Length; i++) + [Fact] + public void TestCheckNeeds__Null() + { + ArgumentNullException ex = Assert.Throws(delegate { - AssertUrlCorrect("SOME_NODE", needsSatisfiedConfigs[i], configs[i + 1]); - } + needsChecker.CheckNeeds(null); + }); - foreach (UrlDir.UrlConfig config in needsUnsatisfiedConfigs) - { - progress.Received().NeedsUnsatisfiedRoot(config); - } + Assert.Equal("mod", ex.ParamName); } [Fact] - public void TestCheckNeeds__Root__KeepsOrder() + public void TestCheckNeeds__Empty() { - string[] modList = { "mod1", "mod2" }; - - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new ConfigNode("NODE_1"), file); - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("NODE_2:NEEDS[mod1]"), file); - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new ConfigNode("NODE_3:NEEDS[mod2]"), file); - UrlDir.UrlConfig config4 = UrlBuilder.CreateConfig(new ConfigNode("NODE_4"), file); - - NeedsChecker.CheckNeeds(root, modList, progress, logger); - - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); - - UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); - Assert.Equal(4, configs.Length); + ArgumentException ex = Assert.Throws(delegate + { + needsChecker.CheckNeeds(""); + }); - Assert.Same(config1, configs[0]); - AssertUrlCorrect("NODE_2", config2, configs[1]); - AssertUrlCorrect("NODE_3", config3, configs[2]); - Assert.Same(config4, configs[3]); + Assert.Equal("mod", ex.ParamName); + Assert.Contains("can't be empty", ex.Message); } [Fact] - public void TestCheckNeeds__Nested() + public void TestCheckNeedsRecursive() { - string[] modList = { "mod1", "mod2" }; - ConfigNode node = new TestConfigNode("SOME_NODE") { { "aa", "00" }, @@ -284,324 +268,121 @@ public void TestCheckNeeds__Nested() }, }; - UrlDir.UrlConfig origUrl = UrlBuilder.CreateConfig(node, file); + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", node); - NeedsChecker.CheckNeeds(root, modList, progress, logger); + needsChecker.CheckNeedsRecursive(node, urlConfig); + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); - UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); - Assert.Equal(1, configs.Length); - - UrlDir.UrlConfig url = configs[0]; - Assert.Equal("SOME_NODE", url.type); - ConfigNode newNode = url.config; - Assert.Equal("SOME_NODE", newNode.name); - - Assert.Equal(2, newNode.values.Count); - Assert.Equal(3, newNode.nodes.Count); + Received.InOrder(delegate + { + progress.NeedsUnsatisfiedValue(urlConfig, "SOME_NODE/cc:NEEDS[mod3]"); + progress.NeedsUnsatisfiedValue(urlConfig, "SOME_NODE/INNER_NODE_2/hh:NEEDS[mod3]"); + progress.NeedsUnsatisfiedNode(urlConfig, "SOME_NODE/INNER_NODE_2/INNER_INNER_NODE_12:NEEDS[mod3]"); + progress.NeedsUnsatisfiedValue(urlConfig, "SOME_NODE/INNER_NODE_3/nn:NEEDS[mod3]"); + progress.NeedsUnsatisfiedNode(urlConfig, "SOME_NODE/INNER_NODE_3/INNER_INNER_NODE_22:NEEDS[mod3]"); + progress.NeedsUnsatisfiedNode(urlConfig, "SOME_NODE/INNER_NODE_4:NEEDS[mod3]"); + }); + + Assert.Equal(2, node.values.Count); + Assert.Equal(3, node.nodes.Count); - Assert.Equal("aa", newNode.values[0].name); - Assert.Equal("00", newNode.values[0].value); + Assert.Equal("aa", node.values[0].name); + Assert.Equal("00", node.values[0].value); - Assert.Equal("bb", newNode.values[1].name); - Assert.Equal("01", newNode.values[1].value); + Assert.Equal("bb", node.values[1].name); + Assert.Equal("01", node.values[1].value); - Assert.Same(node.nodes[0], newNode.nodes[0]); - Assert.Equal("INNER_NODE_1", newNode.nodes[0].name); + Assert.Same(node.nodes[0], node.nodes[0]); + Assert.Equal("INNER_NODE_1", node.nodes[0].name); - Assert.Equal(2, newNode.nodes[0].values.Count); - Assert.Equal(1, newNode.nodes[0].nodes.Count); + Assert.Equal(2, node.nodes[0].values.Count); + Assert.Equal(1, node.nodes[0].nodes.Count); - Assert.Equal("dd", newNode.nodes[0].values[0].name); - Assert.Equal("03", newNode.nodes[0].values[0].value); + Assert.Equal("dd", node.nodes[0].values[0].name); + Assert.Equal("03", node.nodes[0].values[0].value); - Assert.Equal("ee", newNode.nodes[0].values[1].name); - Assert.Equal("04", newNode.nodes[0].values[1].value); + Assert.Equal("ee", node.nodes[0].values[1].name); + Assert.Equal("04", node.nodes[0].values[1].value); - Assert.Equal("INNER_INNER_NODE_1", newNode.nodes[0].nodes[0].name); + Assert.Equal("INNER_INNER_NODE_1", node.nodes[0].nodes[0].name); - Assert.Equal(1, newNode.nodes[0].nodes[0].values.Count); - Assert.Equal(0, newNode.nodes[0].nodes[0].nodes.Count); + Assert.Equal(1, node.nodes[0].nodes[0].values.Count); + Assert.Equal(0, node.nodes[0].nodes[0].nodes.Count); - Assert.Equal("ff", newNode.nodes[0].nodes[0].values[0].name); - Assert.Equal("05", newNode.nodes[0].nodes[0].values[0].value); + Assert.Equal("ff", node.nodes[0].nodes[0].values[0].name); + Assert.Equal("05", node.nodes[0].nodes[0].values[0].value); // Assert.NotSame(node.nodes[1], newNode.nodes[1]); - Assert.Equal("INNER_NODE_2", newNode.nodes[1].name); + Assert.Equal("INNER_NODE_2", node.nodes[1].name); - Assert.Equal(2, newNode.nodes[1].values.Count); - Assert.Equal(2, newNode.nodes[1].nodes.Count); + Assert.Equal(2, node.nodes[1].values.Count); + Assert.Equal(2, node.nodes[1].nodes.Count); - Assert.Equal("gg", newNode.nodes[1].values[0].name); - Assert.Equal("06", newNode.nodes[1].values[0].value); + Assert.Equal("gg", node.nodes[1].values[0].name); + Assert.Equal("06", node.nodes[1].values[0].value); - Assert.Equal("ii", newNode.nodes[1].values[1].name); - Assert.Equal("08", newNode.nodes[1].values[1].value); + Assert.Equal("ii", node.nodes[1].values[1].name); + Assert.Equal("08", node.nodes[1].values[1].value); - Assert.Equal("INNER_INNER_NODE_11", newNode.nodes[1].nodes[0].name); + Assert.Equal("INNER_INNER_NODE_11", node.nodes[1].nodes[0].name); - Assert.Equal("jj", newNode.nodes[1].nodes[0].values[0].name); - Assert.Equal("09", newNode.nodes[1].nodes[0].values[0].value); + Assert.Equal("jj", node.nodes[1].nodes[0].values[0].name); + Assert.Equal("09", node.nodes[1].nodes[0].values[0].value); - Assert.Equal("INNER_INNER_NODE_12", newNode.nodes[1].nodes[1].name); + Assert.Equal("INNER_INNER_NODE_12", node.nodes[1].nodes[1].name); - Assert.Equal("kk", newNode.nodes[1].nodes[1].values[0].name); - Assert.Equal("10", newNode.nodes[1].nodes[1].values[0].value); + Assert.Equal("kk", node.nodes[1].nodes[1].values[0].name); + Assert.Equal("10", node.nodes[1].nodes[1].values[0].value); // Assert.NotSame(node.nodes[1], newNode.nodes[1]); - Assert.Equal("INNER_NODE_3", newNode.nodes[2].name); + Assert.Equal("INNER_NODE_3", node.nodes[2].name); - Assert.Equal(2, newNode.nodes[2].values.Count); - Assert.Equal(2, newNode.nodes[2].nodes.Count); + Assert.Equal(2, node.nodes[2].values.Count); + Assert.Equal(2, node.nodes[2].nodes.Count); - Assert.Equal("mm", newNode.nodes[2].values[0].name); - Assert.Equal("12", newNode.nodes[2].values[0].value); + Assert.Equal("mm", node.nodes[2].values[0].name); + Assert.Equal("12", node.nodes[2].values[0].value); - Assert.Equal("oo", newNode.nodes[2].values[1].name); - Assert.Equal("14", newNode.nodes[2].values[1].value); + Assert.Equal("oo", node.nodes[2].values[1].name); + Assert.Equal("14", node.nodes[2].values[1].value); - Assert.Equal("INNER_INNER_NODE_21", newNode.nodes[2].nodes[0].name); + Assert.Equal("INNER_INNER_NODE_21", node.nodes[2].nodes[0].name); - Assert.Equal("pp", newNode.nodes[2].nodes[0].values[0].name); - Assert.Equal("15", newNode.nodes[2].nodes[0].values[0].value); + Assert.Equal("pp", node.nodes[2].nodes[0].values[0].name); + Assert.Equal("15", node.nodes[2].nodes[0].values[0].value); - Assert.Equal("INNER_INNER_NODE_22", newNode.nodes[2].nodes[1].name); + Assert.Equal("INNER_INNER_NODE_22", node.nodes[2].nodes[1].name); - Assert.Equal("qq", newNode.nodes[2].nodes[1].values[0].name); - Assert.Equal("16", newNode.nodes[2].nodes[1].values[0].value); + Assert.Equal("qq", node.nodes[2].nodes[1].values[0].name); + Assert.Equal("16", node.nodes[2].nodes[1].values[0].value); } - + [Fact] - public void TestCheckNeeds__RootAndNested() + public void TestCheckNeedsRecursive__NodeNull() { - string[] modList = { "mod1", "mod2" }; - - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("SOME_NODE:NEEDS[mod1]") + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + ArgumentNullException ex = Assert.Throws(delegate { - { "aa:NEEDS[mod2]", "00" }, - { "bb:NEEDS[mod3]", "01" }, - new TestConfigNode("INNER_NODE_1:NEEDS[mod2]") - { - { "cc", "02" }, - }, - new TestConfigNode("INNER_NODE_2:NEEDS[mod3]") - { - { "dd", "03" }, - }, - }, file); - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("SOME_OTHER_NODE:NEEDS[mod3]"), file); - - NeedsChecker.CheckNeeds(root, modList, progress, logger); - - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); - - UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); - Assert.Equal(1, configs.Length); - - UrlDir.UrlConfig url = configs[0]; - Assert.Equal("SOME_NODE", url.type); - ConfigNode newNode = url.config; - Assert.Equal("SOME_NODE", newNode.name); - - Assert.Equal(1, newNode.values.Count); - Assert.Equal(1, newNode.nodes.Count); - - Assert.Equal("aa", newNode.values[0].name); - Assert.Equal("00", newNode.values[0].value); - - Assert.Equal("INNER_NODE_1", newNode.nodes[0].name); - - Assert.Equal("cc", newNode.nodes[0].values[0].name); - Assert.Equal("02", newNode.nodes[0].values[0].value); - - progress.Received().NeedsUnsatisfiedRoot(config2); - progress.Received().NeedsUnsatisfiedValue(url, Arg.Is(stack => stack.GetPath() == "SOME_NODE"), "bb:NEEDS[mod3]"); - progress.Received().NeedsUnsatisfiedNode(url, Arg.Is(stack => stack.GetPath() == "SOME_NODE/INNER_NODE_2:NEEDS[mod3]")); - } - - [Fact] - public void TestCheckNeeds__Exception() - { - string[] modList = { "mod1", "mod2" }; + needsChecker.CheckNeedsRecursive(null, urlConfig); + }); - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]"), file); - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); - - Exception e = new Exception(); - progress.When(p => p.NeedsUnsatisfiedRoot(config2)).Throw(e); - - NeedsChecker.CheckNeeds(root, modList, progress, logger); - - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); - - string expected = @" -Exception while checking needs on root node : -abc/def/SOME_NODE:NEEDS[mod3] - SOME_NODE:NEEDS[mod3] - { - } -".Replace("\r", null).TrimStart(); - - progress.Received().Exception(config2, expected, e); - - Assert.Equal(new[] { config1, config3 }, root.AllConfigs); + Assert.Equal("node", ex.ParamName); } [Fact] - public void TestCheckNeeds__Directory() + public void TestCheckNeedsRecursive__UrlConfigNull() { - string[] modList = { "mod1", "mod/2" }; - - UrlBuilder.CreateDir("ghi/jkl", gameData); - - UrlDir.UrlConfig config01 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE01:NEEDS[/abc]"), file); - UrlDir.UrlConfig config02 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE02:NEEDS[abc/]"), file); - UrlDir.UrlConfig config03 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE03:NEEDS[/abc/]"), file); - UrlDir.UrlConfig config04 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE04:NEEDS[ghi/jkl]"), file); - UrlDir.UrlConfig config05 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE05:NEEDS[/ghi/jkl]"), file); - UrlDir.UrlConfig config06 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE06:NEEDS[ghi/jkl/]"), file); - UrlDir.UrlConfig config07 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE07:NEEDS[mod1&ghi/jkl]"), file); - UrlDir.UrlConfig config08 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE08:NEEDS[mod3|ghi/jkl]"), file); - UrlDir.UrlConfig config09 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE09:NEEDS[abc/&ghi/jkl]"), file); - UrlDir.UrlConfig config10 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE10:NEEDS[mod/2]"), file); - - UrlDir.UrlConfig config11 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE11:NEEDS[abc]"), file); - UrlDir.UrlConfig config12 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE12:needs[mod3&ghi/jkl]"), file); - UrlDir.UrlConfig config13 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE13:NEEDS[Ghi/jkl]"), file); - UrlDir.UrlConfig config14 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE14:NEEDS[mno/pqr]"), file); - - NeedsChecker.CheckNeeds(root, modList, progress, logger); - - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); - - UrlDir.UrlConfig[] configs = root.AllConfigs.ToArray(); - Assert.Equal(10, configs.Length); - - AssertUrlCorrect("SOME_NODE01", config01, configs[0]); - AssertUrlCorrect("SOME_NODE02", config02, configs[1]); - AssertUrlCorrect("SOME_NODE03", config03, configs[2]); - AssertUrlCorrect("SOME_NODE04", config04, configs[3]); - AssertUrlCorrect("SOME_NODE05", config05, configs[4]); - AssertUrlCorrect("SOME_NODE06", config06, configs[5]); - AssertUrlCorrect("SOME_NODE07", config07, configs[6]); - AssertUrlCorrect("SOME_NODE08", config08, configs[7]); - AssertUrlCorrect("SOME_NODE09", config09, configs[8]); - AssertUrlCorrect("SOME_NODE09", config09, configs[8]); - AssertUrlCorrect("SOME_NODE10", config10, configs[9]); - - progress.Received().NeedsUnsatisfiedRoot(config11); - progress.Received().NeedsUnsatisfiedRoot(config12); - progress.Received().NeedsUnsatisfiedRoot(config13); - progress.Received().NeedsUnsatisfiedRoot(config14); - } - - [Fact] - public void TestCheckNeeds__ExceptionWhileLoggingException() - { - string[] modList = { "mod1", "mod2" }; - - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE:NEEDS[mod3]"), file); - UrlDir.UrlConfig config3 = UrlBuilder.CreateConfig(new ConfigNode("SOME_NODE"), file); - - Exception e1 = new Exception(); - Exception e2 = new Exception(); - progress.When(p => p.NeedsUnsatisfiedRoot(config2)).Throw(e1); - progress.WhenForAnyArgs(p => p.Exception(null, null, null)).Throw(e2); - - NeedsChecker.CheckNeeds(root, modList, progress, logger); - - progress.ReceivedWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); - - progress.Received().Exception("Exception while attempting to log an exception", e2); - - Assert.Equal(new[] { config1, config3 }, root.AllConfigs); - } - - [Fact] - public void TestCheckNeeds__AllNeedsSatisfied() - { - string[] modList = { "mod1", "mod2" }; - - UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig(new TestConfigNode("SOME_NODE") + ArgumentNullException ex = Assert.Throws(delegate { - { "value", "1" }, - }, file); - UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig(new TestConfigNode("@SOME_NODE") - { - { "@value", "2" }, - { "@value:NEEDS[mod1] +", "4" }, - }, file); - - NeedsChecker.CheckNeeds(root, modList, progress, logger); + needsChecker.CheckNeedsRecursive(new ConfigNode(), null); + }); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - progress.DidNotReceiveWithAnyArgs().Error(null, null); - - ConfigNode node = root.AllConfigs.ToArray().Last().config; - Assert.Equal("@SOME_NODE", node.name); - Assert.Equal("@value", node.values[0].name); - Assert.Equal("2", node.values[0].value); - Assert.Equal("@value +", node.values[1].name); - Assert.Equal("4", node.values[1].value); - - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedValue(null, null, null); - - } - - private UrlDir.UrlConfig CreateConfig(string name) - { - ConfigNode node = new TestConfigNode(name) - { - { "name", "test" }, - { "foo", "bar" }, - new ConfigNode("INNER_NODE"), - }; - - node.id = "who_uses_this"; - - return UrlBuilder.CreateConfig(node, file); - } - - private void AssertUrlCorrect(string expectedNodeName, UrlDir.UrlConfig originalUrl, UrlDir.UrlConfig observedUrl) - { - // Assert.NotSame(originalUrl, observedUrl); - Assert.Equal(expectedNodeName, observedUrl.type); - - ConfigNode originalNode = originalUrl.config; - ConfigNode observedNode = observedUrl.config; - - Assert.Equal(expectedNodeName, observedNode.name); - - if (originalNode.HasValue("name")) Assert.Equal(originalNode.GetValue("name"), observedUrl.name); - - Assert.Same(originalUrl.parent, observedUrl.parent); - - Assert.Equal(originalNode.id, observedNode.id); - Assert.Equal(originalNode.values.Count, observedNode.values.Count); - Assert.Equal(originalNode.nodes.Count, observedNode.nodes.Count); - - for (int i = 0; i < originalNode.values.Count; i++) - { - Assert.Same(originalNode.values[i], observedNode.values[i]); - } - - for (int i = 0; i < originalNode.nodes.Count; i++) - { - Assert.Same(originalNode.nodes[i], observedNode.nodes[i]); - } + Assert.Equal("urlConfig", ex.ParamName); } } } diff --git a/ModuleManagerTests/NodeMatcherTest.cs b/ModuleManagerTests/NodeMatcherTest.cs index 036770cb..866c69fc 100644 --- a/ModuleManagerTests/NodeMatcherTest.cs +++ b/ModuleManagerTests/NodeMatcherTest.cs @@ -10,59 +10,62 @@ public class NodeMatcherTest #region Constructor [Fact] - public void TestConstructor__Null() + public void TestConstructor__TypeNull() { ArgumentNullException ex = Assert.Throws(delegate { - new NodeMatcher(null); + new NodeMatcher(null, null, null); }); - Assert.Equal("nodeName", ex.ParamName); + Assert.Equal("type", ex.ParamName); } [Fact] - public void TestConstructor__Blank() + public void TestConstructor__TypeBlank() { ArgumentException ex = Assert.Throws(delegate { - new NodeMatcher(""); + new NodeMatcher("", null, null); }); - Assert.Equal("nodeName", ex.ParamName); + Assert.Equal("type", ex.ParamName); Assert.Contains("can't be empty", ex.Message); } [Fact] - public void TestConstructor__NotBracketBalanced() + public void TestConstructor__NameBlank() { - FormatException ex = Assert.Throws(delegate + ArgumentException ex = Assert.Throws(delegate { - new NodeMatcher("NODE[stuff"); + new NodeMatcher("NODE", "", null); }); - Assert.Equal("node name is not bracket balanced: NODE[stuff", ex.Message); + Assert.Equal("name", ex.ParamName); + Assert.Contains("can't be empty (null allowed)", ex.Message); } [Fact] - public void TestConstructor__StartsWithHas() + public void TestConstructor__ConstraintsBlank() { - FormatException ex = Assert.Throws(delegate + ArgumentException ex = Assert.Throws(delegate { - new NodeMatcher(":HAS[#blah]"); + new NodeMatcher("NODE", null, ""); }); - Assert.Equal("node name cannot begin with :HAS : :HAS[#blah]", ex.Message); + Assert.Equal("constraints", ex.ParamName); + Assert.Contains("can't be empty (null allowed)", ex.Message); } [Fact] - public void TestConstructor__StartsWithBracket() + public void TestConstructor__ConstraintsNotBracketBalanced() { - FormatException ex = Assert.Throws(delegate + ArgumentException ex = Assert.Throws(delegate { - new NodeMatcher("[#blah]"); + new NodeMatcher("NODE", null, "stuff[blah"); }); - Assert.Equal("node name cannot begin with a bracket: [#blah]", ex.Message); + Assert.Equal("constraints", ex.ParamName); + Assert.Contains("is not bracket balanced: stuff[blah", ex.Message); } #endregion @@ -72,7 +75,7 @@ public void TestConstructor__StartsWithBracket() [Fact] public void TestIsMatch() { - NodeMatcher matcher = new NodeMatcher("NODE"); + NodeMatcher matcher = new NodeMatcher("NODE", null, null); Assert.True(matcher.IsMatch(new ConfigNode("NODE"))); Assert.False(matcher.IsMatch(new ConfigNode("PART"))); @@ -81,7 +84,7 @@ public void TestIsMatch() [Fact] public void TestIsMatch__Name() { - NodeMatcher matcher = new NodeMatcher("NODE[blah]"); + NodeMatcher matcher = new NodeMatcher("NODE", "blah", null); Assert.True(matcher.IsMatch(new TestConfigNode("NODE") { @@ -111,7 +114,7 @@ public void TestIsMatch__Name() [Fact] public void TestIsMatch__Name__Wildcard() { - NodeMatcher matcher = new NodeMatcher("NODE[bl*h]"); + NodeMatcher matcher = new NodeMatcher("NODE", "bl*h", null); Assert.True(matcher.IsMatch(new TestConfigNode("NODE") { @@ -151,7 +154,7 @@ public void TestIsMatch__Name__Wildcard() [Fact] public void TestIsMatch__Name__Multiple() { - NodeMatcher matcher = new NodeMatcher("NODE[blah|bleh|blih*]"); + NodeMatcher matcher = new NodeMatcher("NODE", "blah|bleh|blih*", null); Assert.True(matcher.IsMatch(new TestConfigNode("NODE") { @@ -206,7 +209,7 @@ public void TestIsMatch__Name__Multiple() [Fact] public void TestIsMatch__Constraints() { - NodeMatcher matcher = new NodeMatcher("NODE[blah]:HAS[@FOO[bar*],#something[else]]"); + NodeMatcher matcher = new NodeMatcher("NODE", "blah", "@FOO[bar*],#something[else]"); Assert.True(matcher.IsMatch(new TestConfigNode("NODE") { @@ -262,7 +265,7 @@ public void TestIsMatch__Constraints() [Fact] public void TestIsMatch__Constraints_Open() { - NodeMatcher matcher = new NodeMatcher("NODE[blah]:HAS[@FOO,#something]"); + NodeMatcher matcher = new NodeMatcher("NODE", "blah", "@FOO,#something"); Assert.True(matcher.IsMatch(new TestConfigNode("NODE") { diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index 8d07bd3c..fec4e148 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -13,58 +13,81 @@ namespace ModuleManagerTests { public class PatchApplierTest { - private readonly IBasicLogger logger; - private readonly IPatchProgress progress; - private readonly string[] modList = new[] { "mod1", "mod2" }; - private UrlDir databaseRoot; - private UrlDir.UrlFile file; - private readonly IPass pass1; - private readonly IPass pass2; - private readonly IPass pass3; - private readonly IPatchList patchList; - private readonly PatchApplier patchApplier; - - public PatchApplierTest() + [Fact] + public void TestConstructor__ProgressNull() { - logger = Substitute.For(); - progress = Substitute.For(); - databaseRoot = UrlBuilder.CreateRoot(); - file = UrlBuilder.CreateFile("abc/def.cfg", databaseRoot); - pass1 = Substitute.For(); - pass2 = Substitute.For(); - pass3 = Substitute.For(); - pass1.Name.Returns(":PASS1"); - pass2.Name.Returns(":PASS2"); - pass3.Name.Returns(":PASS3"); - patchList = Substitute.For(); - patchList.GetEnumerator().Returns(new ArrayEnumerator(pass1, pass2, pass3)); - patchApplier = new PatchApplier(patchList, databaseRoot, progress, logger); + ArgumentNullException ex = Assert.Throws(delegate + { + new PatchApplier(null, Substitute.For()); + }); + + Assert.Equal("progress", ex.ParamName); } [Fact] - public void TestApplyPatches() + public void TestConstructor__LoggerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new PatchApplier(Substitute.For(), null); + }); + + Assert.Equal("logger", ex.ParamName); + } + + [Fact] + public void TestApplyPatches__UrlFilesNull() + { + PatchApplier applier = new PatchApplier(Substitute.For(), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + applier.ApplyPatches(null, new IPass[0]); + }); + + Assert.Equal("configFiles", ex.ParamName); + } + + [Fact] + public void TestApplyPatches__PatchesNull() { - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("PART") + PatchApplier applier = new PatchApplier(Substitute.For(), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate { - { "name", "000" }, - { "aaa", "001" }, - }, file); + applier.ApplyPatches(new UrlDir.UrlFile[0], null); + }); + + Assert.Equal("patches", ex.ParamName); + } + + [Fact] + public void TestApplyPatches() + { + IBasicLogger logger = Substitute.For(); + IPatchProgress progress = Substitute.For(); + PatchApplier patchApplier = new PatchApplier(progress, logger); + UrlDir.UrlFile file1 = UrlBuilder.CreateFile("abc/def.cfg"); + UrlDir.UrlFile file2 = UrlBuilder.CreateFile("ghi/jkl.cfg"); + IPass pass1 = Substitute.For(); + IPass pass2 = Substitute.For(); + IPass pass3 = Substitute.For(); + pass1.Name.Returns(":PASS1"); + pass2.Name.Returns(":PASS2"); + pass3.Name.Returns(":PASS3"); UrlDir.UrlConfig[] patchUrlConfigs = new UrlDir.UrlConfig[9]; IPatch[] patches = new IPatch[9]; - - for (int i = 0; i < 9; i++) + for (int i = 0; i < patches.Length; i++) { - patchUrlConfigs[i] = UrlBuilder.CreateConfig(new ConfigNode(), file); patches[i] = Substitute.For(); - patches[i].UrlConfig.Returns(patchUrlConfigs[i]); } pass1.GetEnumerator().Returns(new ArrayEnumerator(patches[0], patches[1], patches[2])); pass2.GetEnumerator().Returns(new ArrayEnumerator(patches[3], patches[4], patches[5])); pass3.GetEnumerator().Returns(new ArrayEnumerator(patches[6], patches[7], patches[8])); - patchApplier.ApplyPatches(); + IPass[] patchList = new IPass[] { pass1, pass2, pass3 }; + + patchApplier.ApplyPatches(new[] { file1, file2 }, new[] { pass1, pass2, pass3 }); progress.DidNotReceiveWithAnyArgs().Error(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); @@ -77,25 +100,34 @@ public void TestApplyPatches() Received.InOrder(delegate { logger.Log(LogType.Log, ":PASS1 pass"); - patches[0].Apply(file, progress, logger); + patches[0].Apply(file1, progress, logger); + patches[0].Apply(file2, progress, logger); progress.PatchApplied(); - patches[1].Apply(file, progress, logger); + patches[1].Apply(file1, progress, logger); + patches[1].Apply(file2, progress, logger); progress.PatchApplied(); - patches[2].Apply(file, progress, logger); + patches[2].Apply(file1, progress, logger); + patches[2].Apply(file2, progress, logger); progress.PatchApplied(); logger.Log(LogType.Log, ":PASS2 pass"); - patches[3].Apply(file, progress, logger); + patches[3].Apply(file1, progress, logger); + patches[3].Apply(file2, progress, logger); progress.PatchApplied(); - patches[4].Apply(file, progress, logger); + patches[4].Apply(file1, progress, logger); + patches[4].Apply(file2, progress, logger); progress.PatchApplied(); - patches[5].Apply(file, progress, logger); + patches[5].Apply(file1, progress, logger); + patches[5].Apply(file2, progress, logger); progress.PatchApplied(); logger.Log(LogType.Log, ":PASS3 pass"); - patches[6].Apply(file, progress, logger); + patches[6].Apply(file1, progress, logger); + patches[6].Apply(file2, progress, logger); progress.PatchApplied(); - patches[7].Apply(file, progress, logger); + patches[7].Apply(file1, progress, logger); + patches[7].Apply(file2, progress, logger); progress.PatchApplied(); - patches[8].Apply(file, progress, logger); + patches[8].Apply(file1, progress, logger); + patches[8].Apply(file2, progress, logger); progress.PatchApplied(); }); } diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 0adcbe90..7f82de56 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -1,719 +1,446 @@ using System; -using System.Collections.Generic; +using System.Linq; using Xunit; using NSubstitute; using TestUtils; using ModuleManager; using ModuleManager.Logging; using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; +using ModuleManager.Tags; namespace ModuleManagerTests { public class PatchExtractorTest { - private UrlDir root; - private UrlDir.UrlFile file; + private readonly UrlDir root; + private readonly UrlDir.UrlFile file; - private IPatchProgress progress; - private IPatchList patchList; - private IBasicLogger logger; - private PatchExtractor extractor; + private readonly IPatchProgress progress; + private readonly IBasicLogger logger; + private readonly INeedsChecker needsChecker; + private readonly ITagListParser tagListParser; + private readonly IProtoPatchBuilder protoPatchBuilder; + private readonly IPatchCompiler patchCompiler; + private readonly PatchExtractor patchExtractor; public PatchExtractorTest() { root = UrlBuilder.CreateRoot(); file = UrlBuilder.CreateFile("abc/def.cfg", root); - - patchList = Substitute.For(); + progress = Substitute.For(); logger = Substitute.For(); - extractor = new PatchExtractor(patchList, progress, logger); + needsChecker = Substitute.For(); + tagListParser = Substitute.For(); + protoPatchBuilder = Substitute.For(); + patchCompiler = Substitute.For(); + patchExtractor = new PatchExtractor(progress, logger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler); } [Fact] - public void TestConstructor__PatchListNull() + public void TestConstructor__ProgressNull() { ArgumentNullException ex = Assert.Throws(delegate { - new PatchExtractor(null, progress, logger); + new PatchExtractor(null, logger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler); }); - Assert.Equal("patchList", ex.ParamName); + Assert.Equal("progress", ex.ParamName); } [Fact] - public void TestConstructor__ProgressNull() + public void TestConstructor__LoggerNull() { ArgumentNullException ex = Assert.Throws(delegate { - new PatchExtractor(patchList, null, logger); + new PatchExtractor(progress, null, needsChecker, tagListParser, protoPatchBuilder, patchCompiler); }); - Assert.Equal("progress", ex.ParamName); + Assert.Equal("logger", ex.ParamName); } [Fact] - public void TestConstructor__LoggerNull() + public void TestConstructor__NeedsCheckerNull() { ArgumentNullException ex = Assert.Throws(delegate { - new PatchExtractor(patchList, progress, null); + new PatchExtractor(progress, logger, null, tagListParser, protoPatchBuilder, patchCompiler); }); - Assert.Equal("logger", ex.ParamName); + Assert.Equal("needsChecker", ex.ParamName); } [Fact] - public void TestExtractPatch__Insert() + public void TestConstructor__TagListParserNull() { - UrlDir.UrlConfig patchConfig = CreateConfig("NODE"); - - extractor.ExtractPatch(patchConfig); - - AssertNoErrors(); - - Assert.Equal(new[] { patchConfig }, root.AllConfigs); - - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - - progress.DidNotReceive().PatchAdded(); + ArgumentNullException ex = Assert.Throws(delegate + { + new PatchExtractor(progress, logger, needsChecker, null, protoPatchBuilder, patchCompiler); + }); - EnsureNeedsSatisfied(); + Assert.Equal("tagListParser", ex.ParamName); } [Fact] - public void TestExtractPatch__First() + public void TestConstructor__ProtoPatchBuilderNull() { - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:FIRST"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FIRST"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:First"); - UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:first"); - - List patches = new List(); - patchList.AddFirstPatch(Arg.Do(patch => patches.Add(patch))); - - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); - extractor.ExtractPatch(patchConfig4); - - AssertNoErrors(); - - Assert.Empty(root.AllConfigs); - - Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); - AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); - - Received.InOrder(delegate + ArgumentNullException ex = Assert.Throws(delegate { - patchList.Received().AddFirstPatch(patches[0]); - progress.Received().PatchAdded(); - patchList.Received().AddFirstPatch(patches[1]); - progress.Received().PatchAdded(); - patchList.Received().AddFirstPatch(patches[2]); - progress.Received().PatchAdded(); - patchList.Received().AddFirstPatch(patches[3]); - progress.Received().PatchAdded(); + new PatchExtractor(progress, logger, needsChecker, tagListParser, null, patchCompiler); }); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - - EnsureNeedsSatisfied(); + Assert.Equal("protoPatchBuilder", ex.ParamName); } [Fact] - public void TestExtractPatch__Legacy() + public void TestConstructor__PatchCompilerNull() { - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE"); - - List patches = new List(); - patchList.AddLegacyPatch(Arg.Do(patch => patches.Add(patch))); - - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - - AssertNoErrors(); - - Assert.Empty(root.AllConfigs); - - Assert.Equal(2, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); - AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); - - Received.InOrder(delegate + ArgumentNullException ex = Assert.Throws(delegate { - patchList.Received().AddLegacyPatch(patches[0]); - progress.Received().PatchAdded(); - patchList.Received().AddLegacyPatch(patches[1]); - progress.Received().PatchAdded(); + new PatchExtractor(progress, logger, needsChecker, tagListParser, protoPatchBuilder, null); }); - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - - EnsureNeedsSatisfied(); + Assert.Equal("patchCompiler", ex.ParamName); } [Fact] - public void TestExtractPatch__BeforeMod() + public void TestExtractPatch__ProtoPatchNull() { - patchList.HasMod("mod1").Returns(true); + UrlDir.UrlConfig patchConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE"), root); - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod1]"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:BEFORE[mod1]"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:Before[mod1]"); - UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:before[MOD1]"); + protoPatchBuilder.Build(patchConfig, Command.Insert, Arg.Any()).Returns(null, new ProtoPatch[0]); - List patches = new List(); - patchList.AddBeforePatch("mod1", Arg.Do(patch => patches.Add(patch))); + Assert.Null(patchExtractor.ExtractPatch(patchConfig)); - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); - extractor.ExtractPatch(patchConfig4); + needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsExpression(null); AssertNoErrors(); Assert.Empty(root.AllConfigs); - Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); - AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); - - Received.InOrder(delegate - { - patchList.Received().AddBeforePatch("mod1", patches[0]); - progress.Received().PatchAdded(); - patchList.Received().AddBeforePatch("mod1", patches[1]); - progress.Received().PatchAdded(); - patchList.Received().AddBeforePatch("mod1", patches[2]); - progress.Received().PatchAdded(); - patchList.Received().AddBeforePatch("mod1", patches[3]); - progress.Received().PatchAdded(); - }); - - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - - EnsureNeedsSatisfied(); + progress.DidNotReceive().PatchAdded(); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } [Fact] - public void TestExtractPatch__BeforeMod__ModDoesNotExist() + public void TestExtractPatch() { - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:BEFORE[mod3]"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:BEFORE[mod3]"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:Before[mod3]"); - UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:before[MOD3]"); + UrlDir.UrlConfig urlConfig = CreateConfig("@NODE_TYPE"); - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); - extractor.ExtractPatch(patchConfig4); + ITagList tagList = Substitute.For(); + tagListParser.Parse("NODE_TYPE").Returns(tagList); - AssertNoErrors(); + IPassSpecifier passSpecifier = Substitute.For(); + ProtoPatch protoPatch = new ProtoPatch( + urlConfig, + Command.Edit, + "NODE_TYPE", + "nodeName", + null, + "has", + passSpecifier + ); - Assert.Empty(root.AllConfigs); + protoPatchBuilder.Build(urlConfig, Command.Edit, tagList).Returns(protoPatch); + passSpecifier.CheckNeeds(needsChecker, progress).Returns(true); - Received.InOrder(delegate - { - progress.Received().NeedsUnsatisfiedBefore(patchConfig1); - progress.Received().NeedsUnsatisfiedBefore(patchConfig2); - progress.Received().NeedsUnsatisfiedBefore(patchConfig3); - progress.Received().NeedsUnsatisfiedBefore(patchConfig4); - }); + IPatch patch = Substitute.For(); + patchCompiler.CompilePatch(protoPatch).Returns(patch); - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + Assert.Same(patch, patchExtractor.ExtractPatch(urlConfig)); - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + AssertNoErrors(); - progress.DidNotReceive().PatchAdded(); + Assert.Empty(root.AllConfigs); + + needsChecker.Received().CheckNeedsRecursive(urlConfig.config, urlConfig); + needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsExpression(null); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } [Fact] - public void TestExtractPatch__ForMod() + public void TestExtractPatch__Needs() { - patchList.HasMod("mod1").Returns(true); - - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod1]"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FOR[mod1]"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:For[mod1]"); - UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:for[MOD1]"); + UrlDir.UrlConfig urlConfig = CreateConfig("@NODE_TYPE"); - List patches = new List(); - patchList.AddForPatch("mod1", Arg.Do(patch => patches.Add(patch))); + ITagList tagList = Substitute.For(); + tagListParser.Parse("NODE_TYPE").Returns(tagList); - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); - extractor.ExtractPatch(patchConfig4); + IPassSpecifier passSpecifier = Substitute.For(); + ProtoPatch protoPatch = new ProtoPatch( + urlConfig, + Command.Edit, + "NODE_TYPE", + "nodeName", + "needs", + "has", + passSpecifier + ); - AssertNoErrors(); - - Assert.Empty(root.AllConfigs); + protoPatchBuilder.Build(urlConfig, Command.Edit, tagList).Returns(protoPatch); + needsChecker.CheckNeedsExpression("needs").Returns(true); + passSpecifier.CheckNeeds(needsChecker, progress).Returns(true); - Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); - AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); + IPatch patch = Substitute.For(); + patchCompiler.CompilePatch(protoPatch).Returns(patch); + Assert.Same(patch, patchExtractor.ExtractPatch(urlConfig)); - Received.InOrder(delegate - { - patchList.Received().AddForPatch("mod1", patches[0]); - progress.Received().PatchAdded(); - patchList.Received().AddForPatch("mod1", patches[1]); - progress.Received().PatchAdded(); - patchList.Received().AddForPatch("mod1", patches[2]); - progress.Received().PatchAdded(); - patchList.Received().AddForPatch("mod1", patches[3]); - progress.Received().PatchAdded(); - }); + AssertNoErrors(); - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + Assert.Empty(root.AllConfigs); - EnsureNeedsSatisfied(); + needsChecker.Received().CheckNeedsRecursive(urlConfig.config, urlConfig); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } [Fact] - public void TestExtractPatch__ForMod__ModDoesNotExist() + public void TestExtractPatch__NeedsUnsatisfied() { - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:FOR[mod3]"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FOR[mod3]"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:For[mod3]"); - UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:for[MOD3]"); + UrlDir.UrlConfig urlConfig = CreateConfig("@NODE_TYPE"); - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); - extractor.ExtractPatch(patchConfig4); + ITagList tagList = Substitute.For(); + tagListParser.Parse("NODE_TYPE").Returns(tagList); - AssertNoErrors(); + IPassSpecifier passSpecifier = Substitute.For(); + ProtoPatch protoPatch = new ProtoPatch( + urlConfig, + Command.Edit, + "NODE_TYPE", + "nodeName", + "needs", + "has", + passSpecifier + ); - Assert.Empty(root.AllConfigs); + protoPatchBuilder.Build(urlConfig, Command.Edit, tagList).Returns(protoPatch); + needsChecker.CheckNeedsExpression("needs").Returns(false); - Received.InOrder(delegate - { - progress.Received().NeedsUnsatisfiedFor(patchConfig1); - progress.Received().NeedsUnsatisfiedFor(patchConfig2); - progress.Received().NeedsUnsatisfiedFor(patchConfig3); - progress.Received().NeedsUnsatisfiedFor(patchConfig4); - }); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null); - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + AssertNoErrors(); - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + Assert.Empty(root.AllConfigs); - progress.DidNotReceive().PatchAdded(); + passSpecifier.DidNotReceiveWithAnyArgs().CheckNeeds(null, null); + needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsRecursive(null, null); + patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null); + + progress.Received().NeedsUnsatisfiedRoot(urlConfig); } [Fact] - public void TestExtractPatch__AfterMod() + public void TestExtractPatch__NeedsUnsatisfiedPassSpecifier() { - patchList.HasMod("mod1").Returns(true); + UrlDir.UrlConfig urlConfig = CreateConfig("@NODE_TYPE"); - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod1]"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:AFTER[mod1]"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:After[mod1]"); - UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:after[MOD1]"); + ITagList tagList = Substitute.For(); + tagListParser.Parse("NODE_TYPE").Returns(tagList); - List patches = new List(); - patchList.AddAfterPatch("mod1", Arg.Do(patch => patches.Add(patch))); + IPassSpecifier passSpecifier = Substitute.For(); + ProtoPatch protoPatch = new ProtoPatch( + urlConfig, + Command.Edit, + "NODE_TYPE", + "nodeName", + "needs", + "has", + passSpecifier + ); - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); - extractor.ExtractPatch(patchConfig4); + protoPatchBuilder.Build(urlConfig, Command.Edit, tagList).Returns(protoPatch); + needsChecker.CheckNeedsExpression("needs").Returns(true); + passSpecifier.CheckNeeds(needsChecker, progress).Returns(false); - AssertNoErrors(); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); - AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); + AssertNoErrors(); Assert.Empty(root.AllConfigs); - Received.InOrder(delegate - { - patchList.Received().AddAfterPatch("mod1", patches[0]); - progress.Received().PatchAdded(); - patchList.Received().AddAfterPatch("mod1", patches[1]); - progress.Received().PatchAdded(); - patchList.Received().AddAfterPatch("mod1", patches[2]); - progress.Received().PatchAdded(); - patchList.Received().AddAfterPatch("mod1", patches[3]); - progress.Received().PatchAdded(); - }); - - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsRecursive(null, null); + patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null); - EnsureNeedsSatisfied(); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } [Fact] - public void TestExtractPatch__AfterMod__ModDoesNotExist() + public void TestExtractPatch__Insert() { - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:AFTER[mod3]"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:AFTER[mod3]"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:After[mod3]"); - UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:after[MOD3]"); - - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); - extractor.ExtractPatch(patchConfig4); - + UrlDir.UrlConfig urlConfig = CreateConfig("NODE_TYPE"); + + ITagList tagList = Substitute.For(); + tagListParser.Parse("NODE_TYPE").Returns(tagList); + + IPassSpecifier passSpecifier = Substitute.For(); + ProtoPatch protoPatch = new ProtoPatch( + urlConfig, + Command.Insert, + "NODE_TYPE", + null, + "needs", + null, + passSpecifier + ); + + protoPatchBuilder.Build(urlConfig, Command.Insert, tagList).Returns(protoPatch); + needsChecker.CheckNeedsExpression("needs").Returns(true); + passSpecifier.CheckNeeds(needsChecker, progress).Returns(true); + + ConfigNode needsCheckedNode = null; + needsChecker.CheckNeedsRecursive(Arg.Do(node => needsCheckedNode = node), urlConfig); + + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); + AssertNoErrors(); - Assert.Empty(root.AllConfigs); - Received.InOrder(delegate { - progress.Received().NeedsUnsatisfiedAfter(patchConfig1); - progress.Received().NeedsUnsatisfiedAfter(patchConfig2); - progress.Received().NeedsUnsatisfiedAfter(patchConfig3); - progress.Received().NeedsUnsatisfiedAfter(patchConfig4); + tagListParser.Parse("NODE_TYPE"); + protoPatchBuilder.Build(urlConfig, Command.Insert, tagList); + needsChecker.CheckNeedsExpression("needs"); + passSpecifier.CheckNeeds(needsChecker, progress); + needsChecker.CheckNeedsRecursive(needsCheckedNode, urlConfig); }); - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null); - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); - - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null); + + Assert.Equal(1, root.AllConfigs.Count()); + UrlDir.UrlConfig newUrlConfig = root.AllConfigs.First(); + Assert.NotSame(urlConfig, newUrlConfig); + Assert.NotSame(urlConfig.config, newUrlConfig.config); + AssertConfigNodesEqual(urlConfig.config, newUrlConfig.config); + Assert.Same(needsCheckedNode, newUrlConfig.config); - progress.DidNotReceive().PatchAdded(); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } [Fact] - public void TestExtractPatch__Final() + public void TestExtractPatch__Null() { - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE[foo]:HAS[#bar]:FINAL"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FINAL"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:Final"); - UrlDir.UrlConfig patchConfig4 = CreateConfig("@NODE:final"); - - List patches = new List(); - patchList.AddFinalPatch(Arg.Do(patch => patches.Add(patch))); - - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); - extractor.ExtractPatch(patchConfig4); - - AssertNoErrors(); - - Assert.Empty(root.AllConfigs); - - Assert.Equal(4, patches.Count); - AssertPatchCorrect(patches[0], patchConfig1, AssertNodeMatcher__Name__Has); - AssertPatchCorrect(patches[1], patchConfig2, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[2], patchConfig3, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[3], patchConfig4, AssertNodeMatcher__Bare); - - Received.InOrder(delegate + ArgumentNullException ex = Assert.Throws(delegate { - patchList.Received().AddFinalPatch(patches[0]); - progress.Received().PatchAdded(); - patchList.Received().AddFinalPatch(patches[1]); - progress.Received().PatchAdded(); - patchList.Received().AddFinalPatch(patches[2]); - progress.Received().PatchAdded(); - patchList.Received().AddFinalPatch(patches[3]); - progress.Received().PatchAdded(); + patchExtractor.ExtractPatch(null); }); - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - - EnsureNeedsSatisfied(); + Assert.Equal("urlConfig", ex.ParamName); } [Fact] - public void TestExtractPatch__InsertWithPass() + public void TestExtractPatch__NotBracketBalanced() { - UrlDir.UrlConfig patchConfig1 = CreateConfig("NODE:FOR[mod1]"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("NODE:FOR[mod2]"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("NODE:FINAL"); - - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); + UrlDir.UrlConfig config1 = CreateConfig("@NODE:FOR["); + UrlDir.UrlConfig config2 = CreateConfig("NODE:HAS[#foo[]"); + patchExtractor.ExtractPatch(config1); + patchExtractor.ExtractPatch(config2); + Assert.Empty(root.AllConfigs); - + progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - + Received.InOrder(delegate { - progress.Received().Error(patchConfig1, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FOR[mod1]"); - progress.Received().Error(patchConfig2, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FOR[mod2]"); - progress.Received().Error(patchConfig3, "Error - pass specifier detected on an insert node (not a patch): abc/def/NODE:FINAL"); + progress.Received().Error(config1, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\nabc/def/@NODE:FOR["); + progress.Received().Error(config2, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\nabc/def/NODE:HAS[#foo[]"); }); - EnsureNeedsSatisfied(); - - progress.DidNotReceive().PatchAdded(); + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } [Fact] - public void TestExtractPatch__MoreThanOnePass() + public void TestExtractPatch__InvalidCommand__Replace() { - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE:FIRST:FIRST"); - UrlDir.UrlConfig patchConfig2 = CreateConfig("@NODE:FIRST:FOR[mod1]"); - UrlDir.UrlConfig patchConfig3 = CreateConfig("@NODE:FOR[mod1]:AFTER[mod2]"); - - extractor.ExtractPatch(patchConfig1); - extractor.ExtractPatch(patchConfig2); - extractor.ExtractPatch(patchConfig3); + UrlDir.UrlConfig urlConfig = CreateConfig("%NODE"); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); Assert.Empty(root.AllConfigs); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - - Received.InOrder(delegate - { - progress.Received().Error(patchConfig1, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FIRST"); - progress.Received().Error(patchConfig2, "Error - more than one pass specifier on a node: abc/def/@NODE:FIRST:FOR[mod1]"); - progress.Received().Error(patchConfig3, "Error - more than one pass specifier on a node: abc/def/@NODE:FOR[mod1]:AFTER[mod2]"); - }); - - EnsureNeedsSatisfied(); - - progress.DidNotReceive().PatchAdded(); + progress.Received().Error(urlConfig, "Error - replace command (%) is not valid on a root node: abc/def/%NODE"); } [Fact] - public void TestExtractPatch__Exception() + public void TestExtractPatch__InvalidCommand__Create() { - Exception e = new Exception("an exception was thrown"); - progress.WhenForAnyArgs(p => p.Error(null, null)).Throw(e); - - UrlDir.UrlConfig patchConfig1 = CreateConfig("@NODE:FIRST:FIRST"); - - extractor.ExtractPatch(patchConfig1); + UrlDir.UrlConfig urlConfig = CreateConfig("&NODE"); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); Assert.Empty(root.AllConfigs); - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); - - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - - progress.Received().Exception(patchConfig1, "Exception while parsing pass for config: abc/def/@NODE:FIRST:FIRST", e); - - EnsureNeedsSatisfied(); - - progress.DidNotReceive().PatchAdded(); + progress.Received().Error(urlConfig, "Error - create command (&) is not valid on a root node: abc/def/&NODE"); } [Fact] - public void TestExtractPatch__NotBracketBalanced() + public void TestExtractPatch__InvalidCommand__Rename() { - UrlDir.UrlConfig config1 = CreateConfig("@NODE:FOR["); - UrlDir.UrlConfig config2 = CreateConfig("NODE:HAS[#foo[]"); - - extractor.ExtractPatch(config1); - extractor.ExtractPatch(config2); + UrlDir.UrlConfig urlConfig = CreateConfig("|NODE"); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); Assert.Empty(root.AllConfigs); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + progress.Received().Error(urlConfig, "Error - rename command (|) is not valid on a root node: abc/def/|NODE"); + } - Received.InOrder(delegate - { - progress.Received().Error(config1, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\nabc/def/@NODE:FOR["); - progress.Received().Error(config2, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\nabc/def/NODE:HAS[#foo[]"); - }); + [Fact] + public void TestExtractPatch__InvalidCommand__Paste() + { + UrlDir.UrlConfig urlConfig = CreateConfig("#NODE"); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - EnsureNeedsSatisfied(); + Assert.Empty(root.AllConfigs); - progress.DidNotReceive().PatchAdded(); + progress.Received().Error(urlConfig, "Error - paste command (#) is not valid on a root node: abc/def/#NODE"); } [Fact] - public void TestExtractPatch__BadlyFormed() + public void TestExtractPatch__InvalidCommand__Special() { - UrlDir.UrlConfig config1 = CreateConfig("@NODE[foo]:HAS[#bar]:FOR[]"); - UrlDir.UrlConfig config2 = CreateConfig("@NODE:BEFORE"); - UrlDir.UrlConfig config3 = CreateConfig("@NODE:AFTER"); - - extractor.ExtractPatch(config1); - extractor.ExtractPatch(config2); - extractor.ExtractPatch(config3); + UrlDir.UrlConfig urlConfig = CreateConfig("*NODE"); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); Assert.Empty(root.AllConfigs); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddForPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + progress.Received().Error(urlConfig, "Error - special command (*) is not valid on a root node: abc/def/*NODE"); + } - Received.InOrder(delegate - { - progress.Received().Error(config1, "Error - malformed :FOR patch specifier detected: abc/def/@NODE[foo]:HAS[#bar]:FOR[]"); - progress.Received().Error(config2, "Error - malformed :BEFORE patch specifier detected: abc/def/@NODE:BEFORE"); - progress.Received().Error(config3, "Error - malformed :AFTER patch specifier detected: abc/def/@NODE:AFTER"); - }); + [Fact] + public void TestExtractPatch__TagListBadlyFormatted() + { + UrlDir.UrlConfig urlConfig = CreateConfig("badSomehow"); + tagListParser.When(t => t.Parse("badSomehow")).Throw(new FormatException("badly formatted")); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - EnsureNeedsSatisfied(); + Assert.Empty(root.AllConfigs); - progress.DidNotReceive().PatchAdded(); + progress.Received().Error(urlConfig, "Cannot parse node name as tag list: badly formatted\non: abc/def/badSomehow"); } [Fact] - public void TestExtractPatch__Command() + public void TestExtractPatch__ProtoPatchFailed() { - patchList.HasMod("mod1").Returns(true); - - UrlDir.UrlConfig config01 = CreateConfig("@NODE:FOR[mod1]"); - UrlDir.UrlConfig config02 = CreateConfig("+NODE:FOR[mod1]"); - UrlDir.UrlConfig config03 = CreateConfig("$NODE:FOR[mod1]"); - UrlDir.UrlConfig config04 = CreateConfig("!NODE:FOR[mod1]"); - UrlDir.UrlConfig config05 = CreateConfig("-NODE:FOR[mod1]"); - UrlDir.UrlConfig config06 = CreateConfig("%NODE:FOR[mod1]"); - UrlDir.UrlConfig config07 = CreateConfig("&NODE:FOR[mod1]"); - UrlDir.UrlConfig config08 = CreateConfig("|NODE:FOR[mod1]"); - UrlDir.UrlConfig config09 = CreateConfig("#NODE:FOR[mod1]"); - UrlDir.UrlConfig config10 = CreateConfig("*NODE:FOR[mod1]"); - - List patches = new List(); - patchList.AddForPatch("mod1", Arg.Do(patch => patches.Add(patch))); - - extractor.ExtractPatch(config01); - extractor.ExtractPatch(config02); - extractor.ExtractPatch(config03); - extractor.ExtractPatch(config04); - extractor.ExtractPatch(config05); - extractor.ExtractPatch(config06); - extractor.ExtractPatch(config07); - extractor.ExtractPatch(config08); - extractor.ExtractPatch(config09); - extractor.ExtractPatch(config10); + UrlDir.UrlConfig urlConfig = CreateConfig("NODE"); + protoPatchBuilder.Build(urlConfig, Command.Insert, Arg.Any()).Returns((ProtoPatch)null); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); - progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + AssertNoErrors(); Assert.Empty(root.AllConfigs); + } - Assert.Equal(5, patches.Count); - AssertPatchCorrect(patches[0], config01, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[1], config02, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[2], config03, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[3], config04, AssertNodeMatcher__Bare); - AssertPatchCorrect(patches[4], config05, AssertNodeMatcher__Bare); - progress.Received().PatchAdded(); - - Received.InOrder(delegate - { - patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[0]); - progress.Received().PatchAdded(); - patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[1]); - progress.Received().PatchAdded(); - patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[2]); - progress.Received().PatchAdded(); - patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[3]); - progress.Received().PatchAdded(); - patchList.DidNotReceiveWithAnyArgs().AddForPatch("mod1", patches[4]); - progress.Received().PatchAdded(); - progress.Received().Error(config06, "Error - replace command (%) is not valid on a root node: abc/def/%NODE:FOR[mod1]"); - progress.Received().Error(config07, "Error - create command (&) is not valid on a root node: abc/def/&NODE:FOR[mod1]"); - progress.Received().Error(config08, "Error - rename command (|) is not valid on a root node: abc/def/|NODE:FOR[mod1]"); - progress.Received().Error(config09, "Error - paste command (#) is not valid on a root node: abc/def/#NODE:FOR[mod1]"); - progress.Received().Error(config10, "Error - special command (*) is not valid on a root node: abc/def/*NODE:FOR[mod1]"); - }); + [Fact] + public void TestExtractPatch__Exception() + { + UrlDir.UrlConfig urlConfig = CreateConfig("NODE"); + Exception ex = new Exception(); + tagListParser.When(t => t.Parse("NODE")).Throw(ex); + Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - EnsureNeedsSatisfied(); + Assert.Empty(root.AllConfigs); - patchList.DidNotReceiveWithAnyArgs().AddFirstPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddLegacyPatch(null); - patchList.DidNotReceiveWithAnyArgs().AddBeforePatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddAfterPatch(null, null); - patchList.DidNotReceiveWithAnyArgs().AddFinalPatch(null); + progress.Received().Exception(urlConfig, "Exception while attempting to create patch from config: abc/def/NODE", ex); } private UrlDir.UrlConfig CreateConfig(string name) @@ -726,20 +453,12 @@ private UrlDir.UrlConfig CreateConfig(string name) new ConfigNode("wine"), new ConfigNode("fruit"), }; - + node.id = "hungry?"; - + return UrlBuilder.CreateConfig(node, file); } - private void AssertPatchCorrect(IPatch patch, UrlDir.UrlConfig originalUrl, Action assertNodeMatcher) where T : IPatch - { - Assert.IsType(patch); - Assert.Same(originalUrl, patch.UrlConfig); - - assertNodeMatcher(patch.NodeMatcher); - } - private void AssertNoErrors() { progress.DidNotReceiveWithAnyArgs().Error(null, null); @@ -747,58 +466,9 @@ private void AssertNoErrors() progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); } - private void EnsureNeedsSatisfied() - { - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null); - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); - } - - private void AssertNodeMatcher__Bare(INodeMatcher matcher) - { - Assert.True(matcher.IsMatch(new ConfigNode("NODE"))); - - Assert.True(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "boo" }, - { "bar", "baz" }, - })); - - Assert.False(matcher.IsMatch(new ConfigNode("NADE"))); - } - - private void AssertNodeMatcher__Name__Has(INodeMatcher matcher) + private void AssertConfigNodesEqual(ConfigNode expected, ConfigNode observed) { - Assert.True(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "foo" }, - { "bar", "baz" }, - })); - - Assert.False(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "foo" }, - })); - - Assert.False(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "boo" }, - { "bar", "baz" }, - })); - - Assert.False(matcher.IsMatch(new ConfigNode("NODE"))); - - Assert.False(matcher.IsMatch(new TestConfigNode("NADE") - { - { "name", "foo" }, - { "bar", "baz" }, - })); - - Assert.False(matcher.IsMatch(new TestConfigNode("NODE") - { - { "name", "boo" }, - { "bar", "baz" }, - })); + Assert.Equal(expected.ToString(), observed.ToString()); } } } diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index 0de0a2ea..d28821a6 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -6,296 +6,164 @@ using TestUtils; using ModuleManager; using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; namespace ModuleManagerTests { public class PatchListTest { - private UrlDir databaseRoot; - private UrlDir.UrlFile file; - private PatchList patchList; - - public PatchListTest() - { - databaseRoot = UrlBuilder.CreateRoot(); - file = UrlBuilder.CreateFile("abc/def.cfg", databaseRoot); - - patchList = new PatchList(new[] { "mod1", "mod2" }); - } - - [Fact] - public void Test__Lifecycle() - { - IPatch patch01 = Substitute.For(); - IPatch patch02 = Substitute.For(); - IPatch patch03 = Substitute.For(); - IPatch patch04 = Substitute.For(); - IPatch patch05 = Substitute.For(); - IPatch patch06 = Substitute.For(); - IPatch patch07 = Substitute.For(); - IPatch patch08 = Substitute.For(); - IPatch patch09 = Substitute.For(); - IPatch patch10 = Substitute.For(); - IPatch patch11 = Substitute.For(); - IPatch patch12 = Substitute.For(); - IPatch patch13 = Substitute.For(); - IPatch patch14 = Substitute.For(); - IPatch patch15 = Substitute.For(); - IPatch patch16 = Substitute.For(); - IPatch patch17 = Substitute.For(); - IPatch patch18 = Substitute.For(); - - patchList.AddFirstPatch(patch01); - patchList.AddFirstPatch(patch02); - patchList.AddLegacyPatch(patch03); - patchList.AddLegacyPatch(patch04); - patchList.AddBeforePatch("mod1", patch05); - patchList.AddBeforePatch("MOD1", patch06); - patchList.AddForPatch("mod1", patch07); - patchList.AddForPatch("MOD1", patch08); - patchList.AddAfterPatch("mod1", patch09); - patchList.AddAfterPatch("MOD1", patch10); - patchList.AddBeforePatch("mod2", patch11); - patchList.AddBeforePatch("MOD2", patch12); - patchList.AddForPatch("mod2", patch13); - patchList.AddForPatch("MOD2", patch14); - patchList.AddAfterPatch("mod2", patch15); - patchList.AddAfterPatch("MOD2", patch16); - patchList.AddFinalPatch(patch17); - patchList.AddFinalPatch(patch18); - - IPass[] passes = patchList.ToArray(); - - Assert.Equal(":FIRST", passes[0].Name); - Assert.Equal(new[] { patch01, patch02 }, passes[0]); - - Assert.Equal(":LEGACY (default)", passes[1].Name); - Assert.Equal(new[] { patch03, patch04 }, passes[1]); - - Assert.Equal(":BEFORE[MOD1]", passes[2].Name); - Assert.Equal(new[] { patch05, patch06 }, passes[2]); - - Assert.Equal(":FOR[MOD1]", passes[3].Name); - Assert.Equal(new[] { patch07, patch08 }, passes[3]); - - Assert.Equal(":AFTER[MOD1]", passes[4].Name); - Assert.Equal(new[] { patch09, patch10 }, passes[4]); - - Assert.Equal(":BEFORE[MOD2]", passes[5].Name); - Assert.Equal(new[] { patch11, patch12 }, passes[5]); - - Assert.Equal(":FOR[MOD2]", passes[6].Name); - Assert.Equal(new[] { patch13, patch14 }, passes[6]); - - Assert.Equal(":AFTER[MOD2]", passes[7].Name); - Assert.Equal(new[] { patch15, patch16 }, passes[7]); - - Assert.Equal(":FINAL", passes[8].Name); - Assert.Equal(new[] { patch17, patch18 }, passes[8]); - } - [Fact] - public void TestHasMod__True() - { - patchList = new PatchList(new[] { "mod1", "Mod2", "MOD3" }); - - Assert.True(patchList.HasMod("mod1")); - Assert.True(patchList.HasMod("Mod1")); - Assert.True(patchList.HasMod("MOD1")); - Assert.True(patchList.HasMod("mod2")); - Assert.True(patchList.HasMod("Mod2")); - Assert.True(patchList.HasMod("MOD2")); - Assert.True(patchList.HasMod("mod3")); - Assert.True(patchList.HasMod("Mod3")); - Assert.True(patchList.HasMod("MOD3")); - } - - [Fact] - public void TestHasMod__False() - { - Assert.False(patchList.HasMod("mod3")); - Assert.False(patchList.HasMod("Mod3")); - Assert.False(patchList.HasMod("MOD3")); - } - - [Fact] - public void TestHasMod__Null() + public void TestConstructor__ModListNull() { ArgumentNullException ex = Assert.Throws(delegate { - Assert.True(patchList.HasMod(null)); - }); - - Assert.Equal("mod", ex.ParamName); - } - - [Fact] - public void TestHasMod__Blank() - { - ArgumentException ex = Assert.Throws(delegate - { - Assert.True(patchList.HasMod("")); + new PatchList(null, new IPatch[0], Substitute.For()); }); - Assert.Contains("can't be empty", ex.Message); - Assert.Equal("mod", ex.ParamName); + Assert.Equal("modList", ex.ParamName); } [Fact] - public void TestAddLegacyPatch__Null() + public void TestConstructor__PatchesNull() { ArgumentNullException ex = Assert.Throws(delegate { - patchList.AddLegacyPatch(null); + new PatchList(new string[0], null, Substitute.For()); }); - Assert.Equal("patch", ex.ParamName); + Assert.Equal("patches", ex.ParamName); } [Fact] - public void TestAddBeforePatch__ModNull() + public void TestConstructor__ProgressNull() { ArgumentNullException ex = Assert.Throws(delegate { - patchList.AddBeforePatch(null, Substitute.For()); + new PatchList(new string[0], new IPatch[0], null); }); - Assert.Equal("mod", ex.ParamName); + Assert.Equal("progress", ex.ParamName); } [Fact] - public void TestAddBeforePatch__ModBlank() + public void TestConstructor__UnknownMod() { - ArgumentException ex = Assert.Throws(delegate - { - patchList.AddBeforePatch("", Substitute.For()); - }); - - Assert.Contains("can't be empty", ex.Message); - Assert.Equal("mod", ex.ParamName); - } + IPatch patch = Substitute.For(); + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + patch.PassSpecifier.Returns(new BeforePassSpecifier("mod3", urlConfig)); + IPatchProgress progress = Substitute.For(); - [Fact] - public void TestAddBeforePatch__PatchNull() - { - ArgumentNullException ex = Assert.Throws(delegate - { - patchList.AddBeforePatch("mod1", null); - }); - - Assert.Equal("patch", ex.ParamName); - } - - [Fact] - public void TestAddBeforePatch__ModDoesNotExist() - { KeyNotFoundException ex = Assert.Throws(delegate { - patchList.AddBeforePatch("mod3", Substitute.For()); + new PatchList(new[] { "mod1", "mod2" }, new[] { patch }, progress); }); Assert.Equal("Mod 'mod3' not found", ex.Message); - } - [Fact] - public void TestAddForPatch__ModNull() - { - ArgumentNullException ex = Assert.Throws(delegate - { - patchList.AddForPatch(null, Substitute.For()); - }); - - Assert.Equal("mod", ex.ParamName); + progress.DidNotReceive().PatchAdded(); } [Fact] - public void TestAddForPatch__ModBlank() + public void TestConstructor__UnknownPassSpecifier() { - ArgumentException ex = Assert.Throws(delegate - { - patchList.AddForPatch("", Substitute.For()); - }); + IPatch patch = Substitute.For(); + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + IPassSpecifier passSpecifier = Substitute.For(); + passSpecifier.Descriptor.Returns(":SOMEPASS"); + patch.PassSpecifier.Returns(passSpecifier); + IPatchProgress progress = Substitute.For(); - Assert.Contains("can't be empty", ex.Message); - Assert.Equal("mod", ex.ParamName); - } - - [Fact] - public void TestAddForPatch__PatchNull() - { - ArgumentNullException ex = Assert.Throws(delegate + NotImplementedException ex = Assert.Throws(delegate { - patchList.AddForPatch("mod1", null); + new PatchList(new string[0], new[] { patch }, progress); }); - Assert.Equal("patch", ex.ParamName); - } + Assert.Equal("Don't know what to do with pass specifier: :SOMEPASS", ex.Message); - [Fact] - public void TestAddForPatch__ModDoesNotExist() - { - KeyNotFoundException ex = Assert.Throws(delegate - { - patchList.AddForPatch("mod3", Substitute.For()); - }); - - Assert.Equal("Mod 'mod3' not found", ex.Message); + progress.DidNotReceive().PatchAdded(); } [Fact] - public void TestAddAfterPatch__ModNull() + public void Test__Lifecycle() { - ArgumentNullException ex = Assert.Throws(delegate - { - patchList.AddAfterPatch(null, Substitute.For()); - }); + IPatch[] patches = new IPatch[] + { + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + }; + + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + + patches[00].PassSpecifier.Returns(new FirstPassSpecifier()); + patches[01].PassSpecifier.Returns(new FirstPassSpecifier()); + patches[02].PassSpecifier.Returns(new LegacyPassSpecifier()); + patches[03].PassSpecifier.Returns(new LegacyPassSpecifier()); + patches[04].PassSpecifier.Returns(new BeforePassSpecifier("mod1", urlConfig)); + patches[05].PassSpecifier.Returns(new BeforePassSpecifier("MOD1", urlConfig)); + patches[06].PassSpecifier.Returns(new ForPassSpecifier("mod1", urlConfig)); + patches[07].PassSpecifier.Returns(new ForPassSpecifier("MOD1", urlConfig)); + patches[08].PassSpecifier.Returns(new AfterPassSpecifier("mod1", urlConfig)); + patches[09].PassSpecifier.Returns(new AfterPassSpecifier("MOD1", urlConfig)); + patches[10].PassSpecifier.Returns(new BeforePassSpecifier("mod2", urlConfig)); + patches[11].PassSpecifier.Returns(new BeforePassSpecifier("MOD2", urlConfig)); + patches[12].PassSpecifier.Returns(new ForPassSpecifier("mod2", urlConfig)); + patches[13].PassSpecifier.Returns(new ForPassSpecifier("MOD2", urlConfig)); + patches[14].PassSpecifier.Returns(new AfterPassSpecifier("mod2", urlConfig)); + patches[15].PassSpecifier.Returns(new AfterPassSpecifier("MOD2", urlConfig)); + patches[16].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[17].PassSpecifier.Returns(new FinalPassSpecifier()); + + IPatchProgress progress = Substitute.For(); + + PatchList patchList = new PatchList(new[] { "mod1", "mod2" }, patches, progress); - Assert.Equal("mod", ex.ParamName); - } + IPass[] passes = patchList.ToArray(); + + Assert.Equal(":FIRST", passes[0].Name); + Assert.Equal(new[] { patches[0], patches[1] }, passes[0]); - [Fact] - public void TestAddAfterPatch__ModBlank() - { - ArgumentException ex = Assert.Throws(delegate - { - patchList.AddAfterPatch("", Substitute.For()); - }); + Assert.Equal(":LEGACY (default)", passes[1].Name); + Assert.Equal(new[] { patches[2], patches[3] }, passes[1]); - Assert.Contains("can't be empty", ex.Message); - Assert.Equal("mod", ex.ParamName); - } + Assert.Equal(":BEFORE[MOD1]", passes[2].Name); + Assert.Equal(new[] { patches[4], patches[5] }, passes[2]); - [Fact] - public void TestAddAfterPatch__PatchNull() - { - ArgumentNullException ex = Assert.Throws(delegate - { - patchList.AddAfterPatch("mod1", null); - }); + Assert.Equal(":FOR[MOD1]", passes[3].Name); + Assert.Equal(new[] { patches[6], patches[7] }, passes[3]); - Assert.Equal("patch", ex.ParamName); - } + Assert.Equal(":AFTER[MOD1]", passes[4].Name); + Assert.Equal(new[] { patches[8], patches[9] }, passes[4]); - [Fact] - public void TestAddAddafterPatch__ModDoesNotExist() - { - KeyNotFoundException ex = Assert.Throws(delegate - { - patchList.AddAfterPatch("mod3", Substitute.For()); - }); + Assert.Equal(":BEFORE[MOD2]", passes[5].Name); + Assert.Equal(new[] { patches[10], patches[11] }, passes[5]); - Assert.Equal("Mod 'mod3' not found", ex.Message); - } + Assert.Equal(":FOR[MOD2]", passes[6].Name); + Assert.Equal(new[] { patches[12], patches[13] }, passes[6]); - [Fact] - public void TestAddFinalPatch__Null() - { - ArgumentNullException ex = Assert.Throws(delegate - { - patchList.AddFinalPatch(null); - }); + Assert.Equal(":AFTER[MOD2]", passes[7].Name); + Assert.Equal(new[] { patches[14], patches[15] }, passes[7]); + + Assert.Equal(":FINAL", passes[8].Name); + Assert.Equal(new[] { patches[16], patches[17] }, passes[8]); - Assert.Equal("patch", ex.ParamName); + progress.Received(patches.Length).PatchAdded(); } } } diff --git a/ModuleManagerTests/Patches/CopyPatchTest.cs b/ModuleManagerTests/Patches/CopyPatchTest.cs index 3b5e1b1e..70eddd6e 100644 --- a/ModuleManagerTests/Patches/CopyPatchTest.cs +++ b/ModuleManagerTests/Patches/CopyPatchTest.cs @@ -5,6 +5,7 @@ using ModuleManager; using ModuleManager.Logging; using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; namespace ModuleManagerTests.Patches @@ -16,7 +17,7 @@ public void TestConstructor__urlConfigNull() { ArgumentNullException ex = Assert.Throws(delegate { - new CopyPatch(null, Substitute.For()); + new CopyPatch(null, Substitute.For(), Substitute.For()); }); Assert.Equal("urlConfig", ex.ParamName); @@ -27,17 +28,28 @@ public void TestConstructor__nodeMatcherNull() { ArgumentNullException ex = Assert.Throws(delegate { - new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null); + new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null, Substitute.For()); }); Assert.Equal("nodeMatcher", ex.ParamName); } + [Fact] + public void TestConstructor__passSpecifierNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), null); + }); + + Assert.Equal("passSpecifier", ex.ParamName); + } + [Fact] public void TestUrlConfig() { UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode()); - CopyPatch patch = new CopyPatch(urlConfig, Substitute.For()); + CopyPatch patch = new CopyPatch(urlConfig, Substitute.For(), Substitute.For()); Assert.Same(urlConfig, patch.UrlConfig); } @@ -46,11 +58,20 @@ public void TestUrlConfig() public void TestNodeMatcher() { INodeMatcher nodeMatcher = Substitute.For(); - CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher); + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher, Substitute.For()); Assert.Same(nodeMatcher, patch.NodeMatcher); } + [Fact] + public void TestPassSpecifier() + { + IPassSpecifier passSpecifier = Substitute.For(); + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), passSpecifier); + + Assert.Same(passSpecifier, patch.PassSpecifier); + } + [Fact] public void TestApply() { @@ -80,7 +101,7 @@ public void TestApply() { { "@foo", "baz" }, { "pqr", "stw" }, - }), nodeMatcher); + }), nodeMatcher, Substitute.For()); IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); @@ -152,7 +173,7 @@ public void TestApply__NameChanged() { "@name", "001" }, { "@foo", "baz" }, { "pqr", "stw" }, - }), nodeMatcher); + }), nodeMatcher, Substitute.For()); IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); @@ -204,7 +225,7 @@ public void TestApply__NameNotChanged() { { "@foo", "baz" }, { "pqr", "stw" }, - }), nodeMatcher); + }), nodeMatcher, Substitute.For()); IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); @@ -233,7 +254,7 @@ public void TestApply__NameNotChanged() [Fact] public void TestApply__FileNull() { - CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(null, Substitute.For(), Substitute.For()); @@ -245,7 +266,7 @@ public void TestApply__FileNull() [Fact] public void TestApply__ProgressNull() { - CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); @@ -257,7 +278,7 @@ public void TestApply__ProgressNull() [Fact] public void TestApply__LoggerNull() { - CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); diff --git a/ModuleManagerTests/Patches/DeletePatchTest.cs b/ModuleManagerTests/Patches/DeletePatchTest.cs index b75576b7..743b7bb2 100644 --- a/ModuleManagerTests/Patches/DeletePatchTest.cs +++ b/ModuleManagerTests/Patches/DeletePatchTest.cs @@ -5,6 +5,7 @@ using ModuleManager; using ModuleManager.Logging; using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; namespace ModuleManagerTests.Patches @@ -16,7 +17,7 @@ public void TestConstructor__urlConfigNull() { ArgumentNullException ex = Assert.Throws(delegate { - new DeletePatch(null, Substitute.For()); + new DeletePatch(null, Substitute.For(), Substitute.For()); }); Assert.Equal("urlConfig", ex.ParamName); @@ -27,17 +28,28 @@ public void TestConstructor__nodeMatcherNull() { ArgumentNullException ex = Assert.Throws(delegate { - new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null); + new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null, Substitute.For()); }); Assert.Equal("nodeMatcher", ex.ParamName); } + [Fact] + public void TestConstructor__passSpecifierNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), null); + }); + + Assert.Equal("passSpecifier", ex.ParamName); + } + [Fact] public void TestUrlConfig() { UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode()); - DeletePatch patch = new DeletePatch(urlConfig, Substitute.For()); + DeletePatch patch = new DeletePatch(urlConfig, Substitute.For(), Substitute.For()); Assert.Same(urlConfig, patch.UrlConfig); } @@ -46,11 +58,20 @@ public void TestUrlConfig() public void TestNodeMatcher() { INodeMatcher nodeMatcher = Substitute.For(); - DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher); + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher, Substitute.For()); Assert.Same(nodeMatcher, patch.NodeMatcher); } + [Fact] + public void TestPassSpecifier() + { + IPassSpecifier passSpecifier = Substitute.For(); + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), passSpecifier); + + Assert.Same(passSpecifier, patch.PassSpecifier); + } + [Fact] public void TestApply() { @@ -68,7 +89,7 @@ public void TestApply() nodeMatcher.IsMatch(urlConfig3.config).Returns(false); nodeMatcher.IsMatch(urlConfig4.config).Returns(true); - DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!NODE")), nodeMatcher); + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!NODE")), nodeMatcher, Substitute.For()); IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); @@ -94,7 +115,7 @@ public void TestApply() [Fact] public void TestApply__FileNull() { - DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(null, Substitute.For(), Substitute.For()); @@ -106,7 +127,7 @@ public void TestApply__FileNull() [Fact] public void TestApply__ProgressNull() { - DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); @@ -118,7 +139,7 @@ public void TestApply__ProgressNull() [Fact] public void TestApply__LoggerNull() { - DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); diff --git a/ModuleManagerTests/Patches/EditPatchTest.cs b/ModuleManagerTests/Patches/EditPatchTest.cs index 7db368e3..d0afbc24 100644 --- a/ModuleManagerTests/Patches/EditPatchTest.cs +++ b/ModuleManagerTests/Patches/EditPatchTest.cs @@ -7,6 +7,7 @@ using ModuleManager; using ModuleManager.Logging; using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; namespace ModuleManagerTests.Patches @@ -18,7 +19,7 @@ public void TestConstructor__urlConfigNull() { ArgumentNullException ex = Assert.Throws(delegate { - new EditPatch(null, Substitute.For()); + new EditPatch(null, Substitute.For(), Substitute.For()); }); Assert.Equal("urlConfig", ex.ParamName); @@ -29,17 +30,28 @@ public void TestConstructor__nodeMatcherNull() { ArgumentNullException ex = Assert.Throws(delegate { - new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null); + new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null, Substitute.For()); }); Assert.Equal("nodeMatcher", ex.ParamName); } + [Fact] + public void TestConstructor__passSpecifierNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), null); + }); + + Assert.Equal("passSpecifier", ex.ParamName); + } + [Fact] public void TestUrlConfig() { UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode()); - EditPatch patch = new EditPatch(urlConfig, Substitute.For()); + EditPatch patch = new EditPatch(urlConfig, Substitute.For(), Substitute.For()); Assert.Same(urlConfig, patch.UrlConfig); } @@ -48,11 +60,20 @@ public void TestUrlConfig() public void TestNodeMatcher() { INodeMatcher nodeMatcher = Substitute.For(); - EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher); + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), nodeMatcher, Substitute.For()); Assert.Same(nodeMatcher, patch.NodeMatcher); } + [Fact] + public void TestPassSpecifier() + { + IPassSpecifier passSpecifier = Substitute.For(); + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), passSpecifier); + + Assert.Same(passSpecifier, patch.PassSpecifier); + } + [Fact] public void TestApply() { @@ -82,7 +103,7 @@ public void TestApply() { { "@foo", "baz" }, { "pqr", "stw" }, - }), nodeMatcher); + }), nodeMatcher, Substitute.For()); IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); @@ -147,7 +168,7 @@ public void TestApply__Loop() { "@aaa *", "2" }, { "bbb", "002" }, new ConfigNode("MM_PATCH_LOOP"), - }), nodeMatcher); + }), nodeMatcher, Substitute.For()); IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); @@ -194,7 +215,7 @@ public void TestApply__Loop() [Fact] public void TestApply__FileNull() { - EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(null, Substitute.For(), Substitute.For()); @@ -206,7 +227,7 @@ public void TestApply__FileNull() [Fact] public void TestApply__ProgressNull() { - EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); @@ -218,7 +239,7 @@ public void TestApply__ProgressNull() [Fact] public void TestApply__LoggerNull() { - EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For()); + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); diff --git a/ModuleManagerTests/Patches/PassSpecifiers/AfterPassSpecifierTest.cs b/ModuleManagerTests/Patches/PassSpecifiers/AfterPassSpecifierTest.cs new file mode 100644 index 00000000..6e086b57 --- /dev/null +++ b/ModuleManagerTests/Patches/PassSpecifiers/AfterPassSpecifierTest.cs @@ -0,0 +1,105 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class AfterPassSpecifierTest + { + public readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + public readonly INeedsChecker needsChecker = Substitute.For(); + public readonly IPatchProgress progress = Substitute.For(); + public readonly AfterPassSpecifier passSpecifier; + + public AfterPassSpecifierTest() + { + passSpecifier = new AfterPassSpecifier("mod1", urlConfig); + } + + [Fact] + public void TestConstructor__ModNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new AfterPassSpecifier(null, urlConfig); + }); + + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestConstructor__ModEmpty() + { + ArgumentException ex = Assert.Throws(delegate + { + new AfterPassSpecifier("", urlConfig); + }); + + Assert.Equal("mod", ex.ParamName); + Assert.Contains("can't be empty", ex.Message); + } + + [Fact] + public void TestConstructor__UrlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new AfterPassSpecifier("mod1", null); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestCheckNeeds__False() + { + needsChecker.CheckNeeds("mod1").Returns(false); + Assert.False(passSpecifier.CheckNeeds(needsChecker, progress)); + + progress.Received().NeedsUnsatisfiedAfter(urlConfig); + } + + [Fact] + public void TestCheckNeeds__True() + { + needsChecker.CheckNeeds("mod1").Returns(true); + Assert.True(passSpecifier.CheckNeeds(needsChecker, progress)); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + } + + [Fact] + public void TestCheckNeeds__NeedsCheckerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(null, progress); + }); + + Assert.Equal("needsChecker", ex.ParamName); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + } + + [Fact] + public void TestCheckNeeds__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(needsChecker, null); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestDescriptor() + { + Assert.Equal(":AFTER[MOD1]", passSpecifier.Descriptor); + } + } +} diff --git a/ModuleManagerTests/Patches/PassSpecifiers/BeforePassSpecifierTest.cs b/ModuleManagerTests/Patches/PassSpecifiers/BeforePassSpecifierTest.cs new file mode 100644 index 00000000..937da7c2 --- /dev/null +++ b/ModuleManagerTests/Patches/PassSpecifiers/BeforePassSpecifierTest.cs @@ -0,0 +1,105 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches.PassSpecifiers +{ + public class BeforePassSpecifierTest + { + public readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + public readonly INeedsChecker needsChecker = Substitute.For(); + public readonly IPatchProgress progress = Substitute.For(); + public readonly BeforePassSpecifier passSpecifier; + + public BeforePassSpecifierTest() + { + passSpecifier = new BeforePassSpecifier("mod1", urlConfig); + } + + [Fact] + public void TestConstructor__ModNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new BeforePassSpecifier(null, urlConfig); + }); + + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestConstructor__ModEmpty() + { + ArgumentException ex = Assert.Throws(delegate + { + new BeforePassSpecifier("", urlConfig); + }); + + Assert.Equal("mod", ex.ParamName); + Assert.Contains("can't be empty", ex.Message); + } + + [Fact] + public void TestConstructor__UrlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new BeforePassSpecifier("mod1", null); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestCheckNeeds__False() + { + needsChecker.CheckNeeds("mod1").Returns(false); + Assert.False(passSpecifier.CheckNeeds(needsChecker, progress)); + + progress.Received().NeedsUnsatisfiedBefore(urlConfig); + } + + [Fact] + public void TestCheckNeeds__True() + { + needsChecker.CheckNeeds("mod1").Returns(true); + Assert.True(passSpecifier.CheckNeeds(needsChecker, progress)); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null); + } + + [Fact] + public void TestCheckNeeds__NeedsCheckerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(null, progress); + }); + + Assert.Equal("needsChecker", ex.ParamName); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedBefore(null); + } + + [Fact] + public void TestCheckNeeds__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(needsChecker, null); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestDescriptor() + { + Assert.Equal(":BEFORE[MOD1]", passSpecifier.Descriptor); + } + } +} diff --git a/ModuleManagerTests/Patches/PassSpecifiers/FinalPassSpecifierTest.cs b/ModuleManagerTests/Patches/PassSpecifiers/FinalPassSpecifierTest.cs new file mode 100644 index 00000000..74b90123 --- /dev/null +++ b/ModuleManagerTests/Patches/PassSpecifiers/FinalPassSpecifierTest.cs @@ -0,0 +1,52 @@ +using System; +using Xunit; +using NSubstitute; +using ModuleManager; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class FinalPassSpecifierrTest + { + public readonly INeedsChecker needsChecker = Substitute.For(); + public readonly IPatchProgress progress = Substitute.For(); + private readonly FinalPassSpecifier passSpecifier = new FinalPassSpecifier(); + + [Fact] + public void TestCheckNeeds() + { + Assert.True(passSpecifier.CheckNeeds(needsChecker, progress)); + } + + [Fact] + public void TestCheckNeeds__NeedsCheckerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(null, progress); + }); + + Assert.Equal("needsChecker", ex.ParamName); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + } + + [Fact] + public void TestCheckNeeds__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(needsChecker, null); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestDescriptor() + { + Assert.Equal(":FINAL", passSpecifier.Descriptor); + } + } +} diff --git a/ModuleManagerTests/Patches/PassSpecifiers/FirstPassSpecifierTest.cs b/ModuleManagerTests/Patches/PassSpecifiers/FirstPassSpecifierTest.cs new file mode 100644 index 00000000..e8d22151 --- /dev/null +++ b/ModuleManagerTests/Patches/PassSpecifiers/FirstPassSpecifierTest.cs @@ -0,0 +1,52 @@ +using System; +using Xunit; +using NSubstitute; +using ModuleManager; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class FirstPassSpecifierTest + { + public readonly INeedsChecker needsChecker = Substitute.For(); + public readonly IPatchProgress progress = Substitute.For(); + private readonly FirstPassSpecifier passSpecifier = new FirstPassSpecifier(); + + [Fact] + public void TestCheckNeeds() + { + Assert.True(passSpecifier.CheckNeeds(needsChecker, progress)); + } + + [Fact] + public void TestCheckNeeds__NeedsCheckerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(null, progress); + }); + + Assert.Equal("needsChecker", ex.ParamName); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + } + + [Fact] + public void TestCheckNeeds__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(needsChecker, null); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestDescriptor() + { + Assert.Equal(":FIRST", passSpecifier.Descriptor); + } + } +} diff --git a/ModuleManagerTests/Patches/PassSpecifiers/ForPassSpecifierTest.cs b/ModuleManagerTests/Patches/PassSpecifiers/ForPassSpecifierTest.cs new file mode 100644 index 00000000..cec25628 --- /dev/null +++ b/ModuleManagerTests/Patches/PassSpecifiers/ForPassSpecifierTest.cs @@ -0,0 +1,105 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class ForPassSpecifierTest + { + public readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + public readonly INeedsChecker needsChecker = Substitute.For(); + public readonly IPatchProgress progress = Substitute.For(); + public readonly ForPassSpecifier passSpecifier; + + public ForPassSpecifierTest() + { + passSpecifier = new ForPassSpecifier("mod1", urlConfig); + } + + [Fact] + public void TestConstructor__ModNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new ForPassSpecifier(null, urlConfig); + }); + + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestConstructor__ModEmpty() + { + ArgumentException ex = Assert.Throws(delegate + { + new ForPassSpecifier("", urlConfig); + }); + + Assert.Equal("mod", ex.ParamName); + Assert.Contains("can't be empty", ex.Message); + } + + [Fact] + public void TestConstructor__UrlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new ForPassSpecifier("mod1", null); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestCheckNeeds__False() + { + needsChecker.CheckNeeds("mod1").Returns(false); + Assert.False(passSpecifier.CheckNeeds(needsChecker, progress)); + + progress.Received().NeedsUnsatisfiedFor(urlConfig); + } + + [Fact] + public void TestCheckNeeds__True() + { + needsChecker.CheckNeeds("mod1").Returns(true); + Assert.True(passSpecifier.CheckNeeds(needsChecker, progress)); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); + } + + [Fact] + public void TestCheckNeeds__NeedsCheckerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(null, progress); + }); + + Assert.Equal("needsChecker", ex.ParamName); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedFor(null); + } + + [Fact] + public void TestCheckNeeds__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(needsChecker, null); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestDescriptor() + { + Assert.Equal(":FOR[MOD1]", passSpecifier.Descriptor); + } + } +} diff --git a/ModuleManagerTests/Patches/PassSpecifiers/InsertPassSpecifierTest.cs b/ModuleManagerTests/Patches/PassSpecifiers/InsertPassSpecifierTest.cs new file mode 100644 index 00000000..9c6cccf1 --- /dev/null +++ b/ModuleManagerTests/Patches/PassSpecifiers/InsertPassSpecifierTest.cs @@ -0,0 +1,52 @@ +using System; +using Xunit; +using NSubstitute; +using ModuleManager; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class InsertPassSpecifierTest + { + public readonly INeedsChecker needsChecker = Substitute.For(); + public readonly IPatchProgress progress = Substitute.For(); + private readonly InsertPassSpecifier passSpecifier = new InsertPassSpecifier(); + + [Fact] + public void TestCheckNeeds() + { + Assert.True(passSpecifier.CheckNeeds(needsChecker, progress)); + } + + [Fact] + public void TestCheckNeeds__NeedsCheckerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(null, progress); + }); + + Assert.Equal("needsChecker", ex.ParamName); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + } + + [Fact] + public void TestCheckNeeds__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(needsChecker, null); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestDescriptor() + { + Assert.Equal(":INSERT (initial)", passSpecifier.Descriptor); + } + } +} diff --git a/ModuleManagerTests/Patches/PassSpecifiers/LegacyPassSpecifierTest.cs b/ModuleManagerTests/Patches/PassSpecifiers/LegacyPassSpecifierTest.cs new file mode 100644 index 00000000..e565655c --- /dev/null +++ b/ModuleManagerTests/Patches/PassSpecifiers/LegacyPassSpecifierTest.cs @@ -0,0 +1,52 @@ +using System; +using Xunit; +using NSubstitute; +using ModuleManager; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class LegacyPassSpecifierTest + { + public readonly INeedsChecker needsChecker = Substitute.For(); + public readonly IPatchProgress progress = Substitute.For(); + private readonly LegacyPassSpecifier passSpecifier = new LegacyPassSpecifier(); + + [Fact] + public void TestCheckNeeds() + { + Assert.True(passSpecifier.CheckNeeds(needsChecker, progress)); + } + + [Fact] + public void TestCheckNeeds__NeedsCheckerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(null, progress); + }); + + Assert.Equal("needsChecker", ex.ParamName); + + progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedAfter(null); + } + + [Fact] + public void TestCheckNeeds__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + passSpecifier.CheckNeeds(needsChecker, null); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestDescriptor() + { + Assert.Equal(":LEGACY (default)", passSpecifier.Descriptor); + } + } +} diff --git a/ModuleManagerTests/Patches/PatchCompilerTest.cs b/ModuleManagerTests/Patches/PatchCompilerTest.cs index 0b401ef3..4484e61b 100644 --- a/ModuleManagerTests/Patches/PatchCompilerTest.cs +++ b/ModuleManagerTests/Patches/PatchCompilerTest.cs @@ -5,6 +5,7 @@ using ModuleManager; using ModuleManager.Logging; using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; namespace ModuleManagerTests.Patches @@ -14,18 +15,27 @@ public class PatchCompilerTest private readonly IPatchProgress progress = Substitute.For(); private readonly IBasicLogger logger = Substitute.For(); private readonly UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); + private readonly PatchCompiler patchCompiler = new PatchCompiler(); [Fact] public void TestCompilePatch__Edit() { - UrlDir.UrlConfig patchUrlConfig = UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") - { - { "@bar", "bleh" }, - }); - - EditPatch patch = Assert.IsType(PatchCompiler.CompilePatch(patchUrlConfig, Command.Edit, "NODE[foo]:HAS[#bar]")); - - Assert.Same(patchUrlConfig, patch.UrlConfig); + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") + { + { "@bar", "bleh" }, + }), + Command.Edit, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + + EditPatch patch = Assert.IsType(patchCompiler.CompilePatch(protoPatch)); + + Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") @@ -38,7 +48,7 @@ public void TestCompilePatch__Edit() AssertNoErrors(); - progress.Received().ApplyingUpdate(urlConfig, patchUrlConfig); + progress.Received().ApplyingUpdate(urlConfig, protoPatch.urlConfig); Assert.Equal(1, file.configs.Count); Assert.NotSame(urlConfig, file.configs[0]); @@ -52,15 +62,23 @@ public void TestCompilePatch__Edit() [Fact] public void TestCompilePatch__Copy() { - UrlDir.UrlConfig patchUrlConfig = UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("+NODE") - { - { "@name", "boo" }, - { "@bar", "bleh" }, - }); - - CopyPatch patch = Assert.IsType(PatchCompiler.CompilePatch(patchUrlConfig, Command.Copy, "NODE[foo]:HAS[#bar]")); - - Assert.Same(patchUrlConfig, patch.UrlConfig); + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("+NODE") + { + { "@name", "boo" }, + { "@bar", "bleh" }, + }), + Command.Copy, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + + CopyPatch patch = Assert.IsType(patchCompiler.CompilePatch(protoPatch)); + + Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") @@ -73,7 +91,7 @@ public void TestCompilePatch__Copy() AssertNoErrors(); - progress.Received().ApplyingCopy(urlConfig, patchUrlConfig); + progress.Received().ApplyingCopy(urlConfig, protoPatch.urlConfig); Assert.Equal(2, file.configs.Count); Assert.Same(urlConfig, file.configs[0]); @@ -92,11 +110,19 @@ public void TestCompilePatch__Copy() [Fact] public void TestCompilePatch__Delete() { - UrlDir.UrlConfig patchUrlConfig = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("-NODE")); - - DeletePatch patch = Assert.IsType(PatchCompiler.CompilePatch(patchUrlConfig, Command.Delete, "NODE[foo]:HAS[#bar]")); - - Assert.Same(patchUrlConfig, patch.UrlConfig); + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("-NODE")), + Command.Delete, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + + DeletePatch patch = Assert.IsType(patchCompiler.CompilePatch(protoPatch)); + + Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") @@ -109,102 +135,129 @@ public void TestCompilePatch__Delete() AssertNoErrors(); - progress.Received().ApplyingDelete(urlConfig, patchUrlConfig); + progress.Received().ApplyingDelete(urlConfig, protoPatch.urlConfig); Assert.Equal(0, file.configs.Count); } [Fact] - public void TestCompilePatch__NullUrlConfig() + public void TestCompilePatch__NullProtoPatch() { ArgumentNullException ex = Assert.Throws(delegate { - PatchCompiler.CompilePatch(null, Command.Edit, "NODE[foo]:HAS[#bar]"); + patchCompiler.CompilePatch(null); }); - Assert.Equal("urlConfig", ex.ParamName); - } - - [Fact] - public void TestCompilePatch__NullName() - { - ArgumentNullException ex = Assert.Throws(delegate - { - PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Edit, null); - }); - - Assert.Equal("name", ex.ParamName); - } - - [Fact] - public void TestCompilePatch__BlankName() - { - ArgumentException ex = Assert.Throws(delegate - { - PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Edit, ""); - }); - - Assert.Equal("name", ex.ParamName); - Assert.Contains("can't be empty", ex.Message); + Assert.Equal("protoPatch", ex.ParamName); } [Fact] public void TestCompilePatch__InvalidCommand__Replace() { + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode()), + Command.Replace, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + ArgumentException ex = Assert.Throws(delegate { - PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Replace, "NODE[foo]:HAS[#bar]"); + patchCompiler.CompilePatch(protoPatch); }); - Assert.Equal("command", ex.ParamName); + Assert.Equal("protoPatch", ex.ParamName); Assert.Contains("invalid command for a root node: Replace", ex.Message); } [Fact] public void TestCompilePatch__InvalidCommand__Create() { + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode()), + Command.Create, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + ArgumentException ex = Assert.Throws(delegate { - PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Create, "NODE[foo]:HAS[#bar]"); + patchCompiler.CompilePatch(protoPatch); }); - Assert.Equal("command", ex.ParamName); + Assert.Equal("protoPatch", ex.ParamName); Assert.Contains("invalid command for a root node: Create", ex.Message); } [Fact] public void TestCompilePatch__InvalidCommand__Rename() { + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode()), + Command.Rename, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + ArgumentException ex = Assert.Throws(delegate { - PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Rename, "NODE[foo]:HAS[#bar]"); + patchCompiler.CompilePatch(protoPatch); }); - Assert.Equal("command", ex.ParamName); + Assert.Equal("protoPatch", ex.ParamName); Assert.Contains("invalid command for a root node: Rename", ex.Message); } [Fact] public void TestCompilePatch__InvalidCommand__Paste() { + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode()), + Command.Paste, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + ArgumentException ex = Assert.Throws(delegate { - PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Paste, "NODE[foo]:HAS[#bar]"); + patchCompiler.CompilePatch(protoPatch); }); - Assert.Equal("command", ex.ParamName); + Assert.Equal("protoPatch", ex.ParamName); Assert.Contains("invalid command for a root node: Paste", ex.Message); } [Fact] public void TestCompilePatch__InvalidCommand__Special() { + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode()), + Command.Special, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + ArgumentException ex = Assert.Throws(delegate { - PatchCompiler.CompilePatch(UrlBuilder.CreateConfig(new ConfigNode(), file), Command.Special, "NODE[foo]:HAS[#bar]"); + patchCompiler.CompilePatch(protoPatch); }); - Assert.Equal("command", ex.ParamName); + Assert.Equal("protoPatch", ex.ParamName); Assert.Contains("invalid command for a root node: Special", ex.Message); } diff --git a/ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs b/ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs new file mode 100644 index 00000000..07477bb4 --- /dev/null +++ b/ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs @@ -0,0 +1,1231 @@ +using System; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Collections; +using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; +using ModuleManager.Tags; + +namespace ModuleManagerTests.Patches +{ + public class ProtoPatchBuilderTest + { + private readonly IPatchProgress progress; + private readonly ProtoPatchBuilder builder; + private readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); + + public ProtoPatchBuilderTest() + { + progress = Substitute.For(); + builder = new ProtoPatchBuilder(progress); + } + + [Fact] + public void TestBuild__PrimaryValueNull() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__PrimaryValue() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", "stuff", null)); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Equal("stuff", protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Needs() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("NEEDS", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Equal("stuff", protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Needs__Case1() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("Needs", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Equal("stuff", protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Needs__Case2() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("needs", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Equal("stuff", protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Has() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("HAS", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Equal("stuff", protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Has__Case1() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("Has", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Equal("stuff", protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Has__Case2() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("has", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Equal("stuff", protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__First() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", null, null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__First__Case1() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("First", null, null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__First__Case2() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("first", null, null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Before() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("BEFORE", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + BeforePassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__Before__Case1() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("Before", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + BeforePassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__Before__Case2() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("before", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + BeforePassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__For() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FOR", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + ForPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__For__Case1() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("For", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + ForPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__For__Case2() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("for", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + ForPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__After() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("AFTER", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + AfterPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__After__Case1() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("After", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + AfterPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__After__Case2() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("after", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + AfterPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + Assert.Same(urlConfig, passSpecifier.urlConfig); + } + + [Fact] + public void TestBuild__Final() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FINAL", null, null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Final__Case1() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("Final", null, null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Final__Case2() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("final", null, null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__Insert__InsertPass() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("NEEDS", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Insert, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Insert, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Equal("stuff", protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__UrlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + builder.Build(null, Command.Edit, Substitute.For()); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestBuild__TagListNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + builder.Build(urlConfig, Command.Edit, null); + }); + + Assert.Equal("tagList", ex.ParamName); + } + + [Fact] + public void TestBuild__PrimaryValueEmpty() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", "", null)); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "empty brackets detected on patch name: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__NodeNameOnInsert() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", "blah", null)); + + Assert.Null(builder.Build(urlConfig, Command.Insert, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "name specifier detected on insert node (not a patch): abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__TrailerOnPrimaryTag() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", "stuff", "otherStuff")); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "unrecognized trailer: 'otherStuff' on: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Equal("stuff", protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__TrailerOnSomeTag() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("NEEDS", "stuff", "morestuff") + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "unrecognized trailer: 'morestuff' on: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Equal("stuff", protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__MoreThanOneNeeds() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("NEEDS", "stuff", null), + new Tag("NEEDS", "otherStuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "more than one :NEEDS tag detected, ignoring all but the first: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Equal("stuff", protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__NullNeeds() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("NEEDS", null, null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :NEEDS tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__EmptyNeeds() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("NEEDS", "", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :NEEDS tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__MoreThanOneHas() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("HAS", "stuff", null), + new Tag("HAS", "otherStuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "more than one :HAS tag detected, ignoring all but the first: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Equal("stuff", protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__NullHas() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("HAS", null, null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :HAS tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__EmptyHas() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("HAS", "", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :HAS tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__HasOnInsert() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("HAS", "", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Insert, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, ":HAS detected on insert node (not a patch): abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__BracketsOnFirst() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", "", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "value detected on :FIRST tag: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__ValueOnFirst() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "value detected on :FIRST tag: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__MoreThanOnePass__First() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FINAL", null, null), + new Tag("FIRST", null, null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__PassSpecifierOnInsert__First() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", null, null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Insert, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "pass specifier detected on insert node (not a patch): abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__NullBefore() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("BEFORE", null, null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :BEFORE tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__EmptyBefore() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("BEFORE", "", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :BEFORE tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__MoreThanOnePass__Before() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", null, null), + new Tag("BEFORE", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__PassSpecifierOnInsert__Before() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("BEFORE", "mod1", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Insert, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "pass specifier detected on insert node (not a patch): abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__NullFor() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FOR", null, null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :FOR tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__EmptyFor() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FOR", "", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :FOR tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__MoreThanOnePass__For() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", null, null), + new Tag("FOR", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__PassSpecifierOnInsert__For() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FOR", "mod1", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Insert, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "pass specifier detected on insert node (not a patch): abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__NullAfter() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("AFTER", null, null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :AFTER tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__EmptyAfter() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("AFTER", "", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :AFTER tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__MoreThanOnePass__After() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", null, null), + new Tag("AFTER", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__PassSpecifierOnInsert__After() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("AFTER", "mod1", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Insert, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "pass specifier detected on insert node (not a patch): abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__BracketsOnFinal() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FINAL", "", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "value detected on :FINAL tag: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__ValueOnFinal() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FINAL", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "value detected on :FINAL tag: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__MoreThanOnePass__Final() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", null, null), + new Tag("FINAL", null, null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__PassSpecifierOnInsert__Final() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FINAL", null, null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Insert, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "pass specifier detected on insert node (not a patch): abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__UnrecognizedTag() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("SOMESTUFF", "blah", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "unrecognized tag: 'SOMESTUFF' on: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + private void EnsureNoErrors() + { + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + } + + private void EnsureNoExceptions() + { + progress.DidNotReceiveWithAnyArgs().Exception(null, null); + progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); + } + } +} diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index cf650285..caaced90 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -134,38 +134,34 @@ public void TestNeedsUnsatisfiedRoot() public void TestNeedsUnsatisfiedNode() { UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); - NodeStack stack1 = new NodeStack(config1.config).Push(new ConfigNode("SOME_CHILD_NODE")); UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); - NodeStack stack2 = new NodeStack(config2.config).Push(new ConfigNode("SOME_OTHER_CHILD_NODE")); Assert.Equal(0, progress.Counter.needsUnsatisfied); - progress.NeedsUnsatisfiedNode(config1, stack1); + progress.NeedsUnsatisfiedNode(config1, "SOME/NODE/PATH/SOME_CHILD_NODE"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting node in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting node in file abc/def subnode: SOME/NODE/PATH/SOME_CHILD_NODE as it can't satisfy its NEEDS"); - progress.NeedsUnsatisfiedNode(config2, stack2); + progress.NeedsUnsatisfiedNode(config2, "SOME/NODE/PATH/SOME_OTHER_CHILD_NODE"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting node in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting node in file ghi/jkl subnode: SOME/NODE/PATH/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); } [Fact] public void TestNeedsUnsatisfiedValue() { UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); - NodeStack stack1 = new NodeStack(config1.config).Push(new ConfigNode("SOME_CHILD_NODE")); UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("SOME_OTHER_NODE")); - NodeStack stack2 = new NodeStack(config2.config).Push(new ConfigNode("SOME_OTHER_CHILD_NODE")); Assert.Equal(0, progress.Counter.needsUnsatisfied); - progress.NeedsUnsatisfiedValue(config1, stack1, "some_value"); + progress.NeedsUnsatisfiedValue(config1, "SOME/NODE/PATH/some_value"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting value in file abc/def subnode: SOME_NODE/SOME_CHILD_NODE value: some_value as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting value in file abc/def value: SOME/NODE/PATH/some_value as it can't satisfy its NEEDS"); - progress.NeedsUnsatisfiedValue(config2, stack2, "some_other_value"); + progress.NeedsUnsatisfiedValue(config2, "SOME/NODE/PATH/some_other_value"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting value in file ghi/jkl subnode: SOME_OTHER_NODE/SOME_OTHER_CHILD_NODE value: some_other_value as it can't satisfy its NEEDS"); + logger.Received().Log(LogType.Log, "Deleting value in file ghi/jkl value: SOME/NODE/PATH/some_other_value as it can't satisfy its NEEDS"); } [Fact] From 845fd212cf7121a33ff68a2d5706126f09391aa8 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 8 Jul 2018 21:39:10 -0700 Subject: [PATCH 222/342] Make extra colons a warning rather than an error Been seeing a lot of these and the correct path is determinate --- ModuleManager/MMPatchLoader.cs | 2 +- ModuleManager/PatchExtractor.cs | 2 +- ModuleManager/Tags/TagListParser.cs | 47 ++++++--- ModuleManagerTests/PatchExtractorTest.cs | 16 +-- ModuleManagerTests/Tags/TagListParserTest.cs | 103 +++++++++++++------ 5 files changed, 111 insertions(+), 59 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 6af7d5d7..882f8e0c 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -167,7 +167,7 @@ private IEnumerator ProcessPatch() UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); INeedsChecker needsChecker = new NeedsChecker(mods, gameData, progress, logger); - ITagListParser tagListParser = new TagListParser(); + ITagListParser tagListParser = new TagListParser(progress); IProtoPatchBuilder protoPatchBuilder = new ProtoPatchBuilder(progress); IPatchCompiler patchCompiler = new PatchCompiler(); PatchExtractor extractor = new PatchExtractor(progress, logger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler); diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 6c604576..3306014d 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -79,7 +79,7 @@ public IPatch ExtractPatch(UrlDir.UrlConfig urlConfig) ITagList tagList; try { - tagList = tagListParser.Parse(name); + tagList = tagListParser.Parse(name, urlConfig); } catch (FormatException ex) { diff --git a/ModuleManager/Tags/TagListParser.cs b/ModuleManager/Tags/TagListParser.cs index b714240e..c9ff1eed 100644 --- a/ModuleManager/Tags/TagListParser.cs +++ b/ModuleManager/Tags/TagListParser.cs @@ -1,29 +1,43 @@ using System; using System.Collections.Generic; +using ModuleManager.Progress; namespace ModuleManager.Tags { public interface ITagListParser { - ITagList Parse(string ToParse); + ITagList Parse(string ToParse, UrlDir.UrlConfig urlConfig); } public class TagListParser : ITagListParser { - public ITagList Parse(string toParse) + private readonly IPatchProgress progress; + + public TagListParser(IPatchProgress progress) + { + this.progress = progress ?? throw new ArgumentNullException(nameof(progress)); + } + + public ITagList Parse(string toParse, UrlDir.UrlConfig urlConfig) { if (toParse == null) throw new ArgumentNullException(nameof(toParse)); + if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig)); if (toParse.Length == 0) throw new FormatException("can't create tag list from empty string"); if (toParse[0] == '[') throw new FormatException("can't create tag list beginning with ["); if (toParse[0] == ':') throw new FormatException("can't create tag list beginning with :"); - if (toParse[toParse.Length - 1] == ':') throw new FormatException("tag list can't end with :"); + + if (toParse[toParse.Length - 1] == ':') + { + progress.Warning(urlConfig, "trailing : detected"); + toParse = toParse.TrimEnd(':'); + } List tags = new List(); - Tag primaryTag = ParsePrimaryTag(toParse, ref tags); + Tag primaryTag = ParsePrimaryTag(toParse, ref tags, urlConfig); return new TagList(primaryTag, tags); } - private static Tag ParsePrimaryTag(string toParse, ref List tags) + private Tag ParsePrimaryTag(string toParse, ref List tags, UrlDir.UrlConfig urlConfig) { for (int i = 1; i < toParse.Length; i++) { @@ -32,11 +46,11 @@ private static Tag ParsePrimaryTag(string toParse, ref List tags) if (c == '[') { int j = ClosingBracketIndex(toParse, i + 1); - return ParsePrimaryTrailer(toParse, j + 1, ref tags, toParse.Substring(0, i), toParse.Substring(i + 1, j - i - 1)); + return ParsePrimaryTrailer(toParse, j + 1, ref tags, toParse.Substring(0, i), toParse.Substring(i + 1, j - i - 1), urlConfig); } else if (c == ':') { - ParseTag(toParse, i + 1, ref tags); + ParseTag(toParse, i + 1, ref tags, urlConfig); return new Tag(toParse.Substring(0, i), null, null); } else if (c == ']') @@ -48,7 +62,7 @@ private static Tag ParsePrimaryTag(string toParse, ref List tags) return new Tag(toParse, null, null); } - private static Tag ParsePrimaryTrailer(string toParse, int start, ref List tags, string primaryKey, string primaryValue) + private Tag ParsePrimaryTrailer(string toParse, int start, ref List tags, string primaryKey, string primaryValue, UrlDir.UrlConfig urlConfig) { for (int i = start; i < toParse.Length; i++) { @@ -57,7 +71,7 @@ private static Tag ParsePrimaryTrailer(string toParse, int start, ref List if (c == ':') { string trailer = i == start ? null : toParse.Substring(start, i - start); - ParseTag(toParse, i + 1, ref tags); + ParseTag(toParse, i + 1, ref tags, urlConfig); return new Tag(primaryKey, primaryValue, trailer); } else if (c == '[') @@ -74,7 +88,7 @@ private static Tag ParsePrimaryTrailer(string toParse, int start, ref List return new Tag(primaryKey, primaryValue, primaryTrailer); } - private static void ParseTag(string toParse, int start, ref List tags) + private void ParseTag(string toParse, int start, ref List tags, UrlDir.UrlConfig urlConfig) { for (int i = start; i < toParse.Length; i++) { @@ -86,16 +100,17 @@ private static void ParseTag(string toParse, int start, ref List tags) throw new FormatException("tag can't start with ["); int j = ClosingBracketIndex(toParse, i + 1); - ParseTrailer(toParse, j + 1, ref tags, toParse.Substring(start, i - start), toParse.Substring(i + 1, j - i - 1)); + ParseTrailer(toParse, j + 1, ref tags, toParse.Substring(start, i - start), toParse.Substring(i + 1, j - i - 1), urlConfig); return; } else if (c == ':') { if (i == start) - throw new FormatException("tag can't start with :"); + progress.Warning(urlConfig, "extra : detected"); + else + tags.Add(new Tag(toParse.Substring(start, i - start), null, null)); - tags.Add(new Tag(toParse.Substring(start, i - start), null, null)); - ParseTag(toParse, i + 1, ref tags); + ParseTag(toParse, i + 1, ref tags, urlConfig); return; } else if (c == ']') @@ -107,7 +122,7 @@ private static void ParseTag(string toParse, int start, ref List tags) tags.Add(new Tag(toParse.Substring(start), null, null)); } - private static void ParseTrailer(string toParse, int start, ref List tags, string key, string value) + private void ParseTrailer(string toParse, int start, ref List tags, string key, string value, UrlDir.UrlConfig urlConfig) { for (int i = start; i < toParse.Length; i++) { @@ -117,7 +132,7 @@ private static void ParseTrailer(string toParse, int start, ref List tags, { string trailer = i == start ? null : toParse.Substring(start, i - start); tags.Add(new Tag(key, value, trailer)); - ParseTag(toParse, i + 1, ref tags); + ParseTag(toParse, i + 1, ref tags, urlConfig); return; } else if (c == '[') diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 7f82de56..e2fab5c0 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -130,7 +130,7 @@ public void TestExtractPatch() UrlDir.UrlConfig urlConfig = CreateConfig("@NODE_TYPE"); ITagList tagList = Substitute.For(); - tagListParser.Parse("NODE_TYPE").Returns(tagList); + tagListParser.Parse("NODE_TYPE", urlConfig).Returns(tagList); IPassSpecifier passSpecifier = Substitute.For(); ProtoPatch protoPatch = new ProtoPatch( @@ -166,7 +166,7 @@ public void TestExtractPatch__Needs() UrlDir.UrlConfig urlConfig = CreateConfig("@NODE_TYPE"); ITagList tagList = Substitute.For(); - tagListParser.Parse("NODE_TYPE").Returns(tagList); + tagListParser.Parse("NODE_TYPE", urlConfig).Returns(tagList); IPassSpecifier passSpecifier = Substitute.For(); ProtoPatch protoPatch = new ProtoPatch( @@ -202,7 +202,7 @@ public void TestExtractPatch__NeedsUnsatisfied() UrlDir.UrlConfig urlConfig = CreateConfig("@NODE_TYPE"); ITagList tagList = Substitute.For(); - tagListParser.Parse("NODE_TYPE").Returns(tagList); + tagListParser.Parse("NODE_TYPE", urlConfig).Returns(tagList); IPassSpecifier passSpecifier = Substitute.For(); ProtoPatch protoPatch = new ProtoPatch( @@ -237,7 +237,7 @@ public void TestExtractPatch__NeedsUnsatisfiedPassSpecifier() UrlDir.UrlConfig urlConfig = CreateConfig("@NODE_TYPE"); ITagList tagList = Substitute.For(); - tagListParser.Parse("NODE_TYPE").Returns(tagList); + tagListParser.Parse("NODE_TYPE", urlConfig).Returns(tagList); IPassSpecifier passSpecifier = Substitute.For(); ProtoPatch protoPatch = new ProtoPatch( @@ -272,7 +272,7 @@ public void TestExtractPatch__Insert() UrlDir.UrlConfig urlConfig = CreateConfig("NODE_TYPE"); ITagList tagList = Substitute.For(); - tagListParser.Parse("NODE_TYPE").Returns(tagList); + tagListParser.Parse("NODE_TYPE", urlConfig).Returns(tagList); IPassSpecifier passSpecifier = Substitute.For(); ProtoPatch protoPatch = new ProtoPatch( @@ -298,7 +298,7 @@ public void TestExtractPatch__Insert() Received.InOrder(delegate { - tagListParser.Parse("NODE_TYPE"); + tagListParser.Parse("NODE_TYPE", urlConfig); protoPatchBuilder.Build(urlConfig, Command.Insert, tagList); needsChecker.CheckNeedsExpression("needs"); passSpecifier.CheckNeeds(needsChecker, progress); @@ -410,7 +410,7 @@ public void TestExtractPatch__InvalidCommand__Special() public void TestExtractPatch__TagListBadlyFormatted() { UrlDir.UrlConfig urlConfig = CreateConfig("badSomehow"); - tagListParser.When(t => t.Parse("badSomehow")).Throw(new FormatException("badly formatted")); + tagListParser.When(t => t.Parse("badSomehow", urlConfig)).Throw(new FormatException("badly formatted")); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); Assert.Empty(root.AllConfigs); @@ -435,7 +435,7 @@ public void TestExtractPatch__Exception() { UrlDir.UrlConfig urlConfig = CreateConfig("NODE"); Exception ex = new Exception(); - tagListParser.When(t => t.Parse("NODE")).Throw(ex); + tagListParser.When(t => t.Parse("NODE", urlConfig)).Throw(ex); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); Assert.Empty(root.AllConfigs); diff --git a/ModuleManagerTests/Tags/TagListParserTest.cs b/ModuleManagerTests/Tags/TagListParserTest.cs index b13277e8..9ee395d0 100644 --- a/ModuleManagerTests/Tags/TagListParserTest.cs +++ b/ModuleManagerTests/Tags/TagListParserTest.cs @@ -1,17 +1,38 @@ using System; using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager.Progress; using ModuleManager.Tags; namespace ModuleManagerTests.Tags { public class TagListParserTest { - private readonly TagListParser tagListParser = new TagListParser(); + private readonly IPatchProgress progress = Substitute.For(); + private readonly TagListParser tagListParser; + private readonly UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def.cfg", new ConfigNode("BLAH")); + + public TagListParserTest() + { + tagListParser = new TagListParser(progress); + } + + [Fact] + public void TestConstructor__ProgressNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new TagListParser(null); + }); + + Assert.Equal("progress", ex.ParamName); + } [Fact] public void TestParse__OnlyPrimaryKey() { - ITagList tagList = tagListParser.Parse("01"); + ITagList tagList = tagListParser.Parse("01", urlConfig); Assert.Equal(new Tag("01", null, null), tagList.PrimaryTag); Assert.Empty(tagList); } @@ -19,7 +40,7 @@ public void TestParse__OnlyPrimaryKey() [Fact] public void TestParse__OnlyPrimaryKeyAndValue() { - ITagList tagList = tagListParser.Parse("01[02]"); + ITagList tagList = tagListParser.Parse("01[02]", urlConfig); Assert.Equal(new Tag("01", "02", null), tagList.PrimaryTag); Assert.Empty(tagList); } @@ -27,7 +48,7 @@ public void TestParse__OnlyPrimaryKeyAndValue() [Fact] public void TestParse__OnlyPrimaryKeyValueAndTrailer() { - ITagList tagList = tagListParser.Parse("01[02]03"); + ITagList tagList = tagListParser.Parse("01[02]03", urlConfig); Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); Assert.Empty(tagList); } @@ -35,7 +56,7 @@ public void TestParse__OnlyPrimaryKeyValueAndTrailer() [Fact] public void TestParse__OnlyPrimaryKeyValueAndTrailer__ValueHasSomeStuff() { - ITagList tagList = tagListParser.Parse("01[02:[03:04[05]]]06"); + ITagList tagList = tagListParser.Parse("01[02:[03:04[05]]]06", urlConfig); Assert.Equal(new Tag("01", "02:[03:04[05]]", "06"), tagList.PrimaryTag); Assert.Empty(tagList); } @@ -43,7 +64,7 @@ public void TestParse__OnlyPrimaryKeyValueAndTrailer__ValueHasSomeStuff() [Fact] public void TestParse__TagWithOnlyKey() { - ITagList tagList = tagListParser.Parse("01[02]03:04:05"); + ITagList tagList = tagListParser.Parse("01[02]03:04:05", urlConfig); Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); Assert.Equal(new[] { new Tag("04", null, null), @@ -54,7 +75,7 @@ public void TestParse__TagWithOnlyKey() [Fact] public void TestParse__TagWithOnlyKeyAndValue() { - ITagList tagList = tagListParser.Parse("01[02]03:04[05]:06[07]"); + ITagList tagList = tagListParser.Parse("01[02]03:04[05]:06[07]", urlConfig); Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); Assert.Equal(new[] { new Tag("04", "05", null), @@ -65,7 +86,7 @@ public void TestParse__TagWithOnlyKeyAndValue() [Fact] public void TestParse__FullTags() { - ITagList tagList = tagListParser.Parse("01[02]03:04[05]06:07[08]09"); + ITagList tagList = tagListParser.Parse("01[02]03:04[05]06:07[08]09", urlConfig); Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); Assert.Equal(new[] { new Tag("04", "05", "06"), @@ -76,7 +97,7 @@ public void TestParse__FullTags() [Fact] public void TestParse__MixedTags() { - ITagList tagList = tagListParser.Parse("01[02]03:04:05[06]:07[08]09"); + ITagList tagList = tagListParser.Parse("01[02]03:04:05[06]:07[08]09", urlConfig); Assert.Equal(new Tag("01", "02", "03"), tagList.PrimaryTag); Assert.Equal(new[] { new Tag("04", null, null), @@ -86,22 +107,33 @@ public void TestParse__MixedTags() } [Fact] - public void TestParse__Null() + public void TestParse__StringNull() { ArgumentNullException ex = Assert.Throws(delegate { - tagListParser.Parse(null); + tagListParser.Parse(null, urlConfig); }); Assert.Equal("toParse", ex.ParamName); } + [Fact] + public void TestParse__UrlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + tagListParser.Parse("BLAH", null); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + [Fact] public void TestParse__Empty() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse(""); + tagListParser.Parse("", urlConfig); }); Assert.Equal("can't create tag list from empty string", ex.Message); @@ -112,7 +144,7 @@ public void TestParse__StartsWithOpenBracket() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("[stuff]"); + tagListParser.Parse("[stuff]", urlConfig); }); Assert.Equal("can't create tag list beginning with [", ex.Message); @@ -123,7 +155,7 @@ public void TestParse__StartsWithColon() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse(":stuff"); + tagListParser.Parse(":stuff", urlConfig); }); Assert.Equal("can't create tag list beginning with :", ex.Message); @@ -132,12 +164,14 @@ public void TestParse__StartsWithColon() [Fact] public void TestParse__EndsWithColon() { - FormatException ex = Assert.Throws(delegate - { - tagListParser.Parse("stuff:blah:"); - }); + ITagList tagList = tagListParser.Parse("stuff:blah::", urlConfig); - Assert.Equal("tag list can't end with :", ex.Message); + progress.Received().Warning(urlConfig, "trailing : detected"); + + Assert.Equal(new Tag("stuff", null, null), tagList.PrimaryTag); + Assert.Equal(new[] { + new Tag("blah", null, null), + }, tagList); } [Fact] @@ -145,7 +179,7 @@ public void TestParse__ClosingBracketInPrimaryKey() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc]def"); + tagListParser.Parse("abc]def", urlConfig); }); Assert.Equal("encountered closing bracket in primary key", ex.Message); @@ -156,7 +190,7 @@ public void TestParse__PrimaryValueHasNoClosingBracket() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc[def[ghi]jkl"); + tagListParser.Parse("abc[def[ghi]jkl", urlConfig); }); Assert.Equal("reached end of the tag list without encountering a close bracket", ex.Message); @@ -167,7 +201,7 @@ public void TestParse__OpeningBracketInPrimaryTrailer() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc[def]ghi[jkl]"); + tagListParser.Parse("abc[def]ghi[jkl]", urlConfig); }); Assert.Equal("encountered opening bracket in primary trailer", ex.Message); @@ -178,7 +212,7 @@ public void TestParse__ClosingBracketInPrimaryTrailer() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc[def]ghi]jkl"); + tagListParser.Parse("abc[def]ghi]jkl", urlConfig); }); Assert.Equal("encountered closing bracket in primary trailer", ex.Message); @@ -189,7 +223,7 @@ public void TestParse__TagStartsWithOpenBracket() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc:def:[ghi]"); + tagListParser.Parse("abc:def:[ghi]", urlConfig); }); Assert.Equal("tag can't start with [", ex.Message); @@ -198,12 +232,15 @@ public void TestParse__TagStartsWithOpenBracket() [Fact] public void TestParse__TagStartsWithColon() { - FormatException ex = Assert.Throws(delegate - { - tagListParser.Parse("abc:def::ghi"); - }); + ITagList tagList = tagListParser.Parse("abc:def::ghi", urlConfig); - Assert.Equal("tag can't start with :", ex.Message); + progress.Received().Warning(urlConfig, "extra : detected"); + + Assert.Equal(new Tag("abc", null, null), tagList.PrimaryTag); + Assert.Equal(new[] { + new Tag("def", null, null), + new Tag("ghi", null, null), + }, tagList); } [Fact] @@ -211,7 +248,7 @@ public void TestParse__ClosingBracketInKey() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc:def:ghi]jkl"); + tagListParser.Parse("abc:def:ghi]jkl", urlConfig); }); Assert.Equal("encountered closing bracket in key", ex.Message); @@ -222,7 +259,7 @@ public void TestParse__ValueHasNoClosingBracket() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc:def:ghi[jkl[mno]pqr"); + tagListParser.Parse("abc:def:ghi[jkl[mno]pqr", urlConfig); }); Assert.Equal("reached end of the tag list without encountering a close bracket", ex.Message); @@ -233,7 +270,7 @@ public void TestParse__OpeningBracketInTrailer() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc:def:ghi[jkl]mno[pqr]"); + tagListParser.Parse("abc:def:ghi[jkl]mno[pqr]", urlConfig); }); Assert.Equal("encountered opening bracket in trailer", ex.Message); @@ -244,7 +281,7 @@ public void TestParse__ClosingBracketInTrailer() { FormatException ex = Assert.Throws(delegate { - tagListParser.Parse("abc:def:ghi[jkl]mno]pqr"); + tagListParser.Parse("abc:def:ghi[jkl]mno]pqr", urlConfig); }); Assert.Equal("encountered closing bracket in trailer", ex.Message); From f00933615ce403b866dd0bf5c0a0f2d62d544073 Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Sun, 11 Nov 2018 12:30:32 -0800 Subject: [PATCH 223/342] Implement :LAST[mod] pass --- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/PatchList.cs | 17 ++- .../PassSpecifiers/LastPassSpecifier.cs | 19 +++ ModuleManager/Patches/ProtoPatchBuilder.cs | 31 +++- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PatchListTest.cs | 44 ++++-- .../PassSpecifiers/LastPassSpecifierTest.cs | 58 +++++++ .../Patches/ProtoPatchBuilderTest.cs | 142 ++++++++++++++++++ 8 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 ModuleManager/Patches/PassSpecifiers/LastPassSpecifier.cs create mode 100644 ModuleManagerTests/Patches/PassSpecifiers/LastPassSpecifierTest.cs diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 57fc26ae..1e5efa63 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -71,6 +71,7 @@ + diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index 6c44d0b3..e600a7c0 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -17,7 +17,8 @@ private class ModPass public readonly Pass beforePass; public readonly Pass forPass; public readonly Pass afterPass; - + public readonly Pass lastPass; + public ModPass(string name) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -27,11 +28,13 @@ public ModPass(string name) beforePass = new Pass($":BEFORE[{this.name}]"); forPass = new Pass($":FOR[{this.name}]"); afterPass = new Pass($":AFTER[{this.name}]"); + lastPass = new Pass($":LAST[{this.name}]"); } public void AddBeforePatch(IPatch patch) => beforePass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); public void AddForPatch(IPatch patch) => forPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); public void AddAfterPatch(IPatch patch) => afterPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); + public void AddLastPatch(IPatch patch) => lastPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); } private class ModPassCollection : IEnumerable @@ -104,6 +107,11 @@ public PatchList(IEnumerable modList, IEnumerable patches, IPatc EnsureMod(afterPassSpecifier.mod); modPasses[afterPassSpecifier.mod].AddAfterPatch(patch); } + else if (patch.PassSpecifier is LastPassSpecifier lastPassSpecifier) + { + EnsureMod(lastPassSpecifier.mod); + modPasses[lastPassSpecifier.mod].AddLastPatch(patch); + } else if (patch.PassSpecifier is FinalPassSpecifier) { finalPatches.Add(patch); @@ -123,7 +131,7 @@ public PatchList(IEnumerable modList, IEnumerable patches, IPatc private IPass[] EnumeratePasses() { - IPass[] result = new IPass[modPasses.Count * 3 + 3]; + IPass[] result = new IPass[modPasses.Count * 4 + 3]; result[0] = firstPatches; result[1] = legacyPatches; @@ -135,6 +143,11 @@ private IPass[] EnumeratePasses() result[i * 3 + 4] = modPasses[i].afterPass; } + for (int i = 0; i < modPasses.Count; i++) + { + result[2 + (modPasses.Count * 3) + i] = modPasses[i].lastPass; + } + result[result.Length - 1] = finalPatches; return result; diff --git a/ModuleManager/Patches/PassSpecifiers/LastPassSpecifier.cs b/ModuleManager/Patches/PassSpecifiers/LastPassSpecifier.cs new file mode 100644 index 00000000..e0d4fcc4 --- /dev/null +++ b/ModuleManager/Patches/PassSpecifiers/LastPassSpecifier.cs @@ -0,0 +1,19 @@ +using System; +using ModuleManager.Progress; + +namespace ModuleManager.Patches.PassSpecifiers +{ + public class LastPassSpecifier : IPassSpecifier + { + public readonly string mod; + + public LastPassSpecifier(string mod) + { + if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); + this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); + } + + public bool CheckNeeds(INeedsChecker needsChecker, IPatchProgress progress) => true; + public string Descriptor => $":LAST[{mod.ToUpper()}]"; + } +} diff --git a/ModuleManager/Patches/ProtoPatchBuilder.cs b/ModuleManager/Patches/ProtoPatchBuilder.cs index 7979aac3..377855fa 100644 --- a/ModuleManager/Patches/ProtoPatchBuilder.cs +++ b/ModuleManager/Patches/ProtoPatchBuilder.cs @@ -25,7 +25,7 @@ public ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList ta if (urlConfig == null) throw new ArgumentNullException(nameof(urlConfig)); if (tagList == null) throw new ArgumentNullException(nameof(tagList)); if (progress == null) throw new ArgumentNullException(nameof(progress)); - + bool error = false; string nodeType = tagList.PrimaryTag.key; @@ -42,7 +42,7 @@ public ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList ta progress.Warning(urlConfig, "empty brackets detected on patch name: " + urlConfig.SafeUrl()); nodeName = null; } - + if (tagList.PrimaryTag.trailer != null) progress.Warning(urlConfig, "unrecognized trailer: '" + tagList.PrimaryTag.trailer + "' on: " + urlConfig.SafeUrl()); @@ -52,7 +52,7 @@ public ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList ta foreach (Tag tag in tagList) { - if (tag.trailer != null) + if (tag.trailer != null) progress.Warning(urlConfig, "unrecognized trailer: '" + tag.trailer + "' on: " + urlConfig.SafeUrl()); if (tag.key.Equals("NEEDS", StringComparison.CurrentCultureIgnoreCase)) @@ -122,7 +122,7 @@ public ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList ta error = true; continue; } - + if (command == Command.Insert) { progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl()); @@ -183,6 +183,29 @@ public ProtoPatch Build(UrlDir.UrlConfig urlConfig, Command command, ITagList ta passSpecifier = new AfterPassSpecifier(tag.value, urlConfig); } + else if (tag.key.Equals("LAST", StringComparison.CurrentCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(tag.value)) + { + progress.Error(urlConfig, "empty :LAST tag detected: " + urlConfig.SafeUrl()); + error = true; + continue; + } + + if (command == Command.Insert) + { + progress.Error(urlConfig, "pass specifier detected on insert node (not a patch): " + urlConfig.SafeUrl()); + error = true; + continue; + } + if (passSpecifier != null) + { + progress.Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: " + urlConfig.SafeUrl()); + continue; + } + + passSpecifier = new LastPassSpecifier(tag.value); + } else if (tag.key.Equals("FINAL", StringComparison.CurrentCultureIgnoreCase)) { if (tag.value != null) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 6e1a83bf..654b7839 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -74,6 +74,7 @@ + diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index d28821a6..d21c8c3f 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -107,6 +107,10 @@ public void Test__Lifecycle() Substitute.For(), Substitute.For(), Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), }; UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); @@ -121,21 +125,27 @@ public void Test__Lifecycle() patches[07].PassSpecifier.Returns(new ForPassSpecifier("MOD1", urlConfig)); patches[08].PassSpecifier.Returns(new AfterPassSpecifier("mod1", urlConfig)); patches[09].PassSpecifier.Returns(new AfterPassSpecifier("MOD1", urlConfig)); - patches[10].PassSpecifier.Returns(new BeforePassSpecifier("mod2", urlConfig)); - patches[11].PassSpecifier.Returns(new BeforePassSpecifier("MOD2", urlConfig)); - patches[12].PassSpecifier.Returns(new ForPassSpecifier("mod2", urlConfig)); - patches[13].PassSpecifier.Returns(new ForPassSpecifier("MOD2", urlConfig)); - patches[14].PassSpecifier.Returns(new AfterPassSpecifier("mod2", urlConfig)); - patches[15].PassSpecifier.Returns(new AfterPassSpecifier("MOD2", urlConfig)); - patches[16].PassSpecifier.Returns(new FinalPassSpecifier()); - patches[17].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[10].PassSpecifier.Returns(new LastPassSpecifier("mod1")); + patches[11].PassSpecifier.Returns(new LastPassSpecifier("MOD1")); + patches[12].PassSpecifier.Returns(new BeforePassSpecifier("mod2", urlConfig)); + patches[13].PassSpecifier.Returns(new BeforePassSpecifier("MOD2", urlConfig)); + patches[14].PassSpecifier.Returns(new ForPassSpecifier("mod2", urlConfig)); + patches[15].PassSpecifier.Returns(new ForPassSpecifier("MOD2", urlConfig)); + patches[16].PassSpecifier.Returns(new AfterPassSpecifier("mod2", urlConfig)); + patches[17].PassSpecifier.Returns(new AfterPassSpecifier("MOD2", urlConfig)); + patches[18].PassSpecifier.Returns(new LastPassSpecifier("mod2")); + patches[19].PassSpecifier.Returns(new LastPassSpecifier("MOD2")); + patches[20].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[21].PassSpecifier.Returns(new FinalPassSpecifier()); IPatchProgress progress = Substitute.For(); PatchList patchList = new PatchList(new[] { "mod1", "mod2" }, patches, progress); IPass[] passes = patchList.ToArray(); - + + Assert.Equal(11, passes.Length); + Assert.Equal(":FIRST", passes[0].Name); Assert.Equal(new[] { patches[0], patches[1] }, passes[0]); @@ -152,16 +162,22 @@ public void Test__Lifecycle() Assert.Equal(new[] { patches[8], patches[9] }, passes[4]); Assert.Equal(":BEFORE[MOD2]", passes[5].Name); - Assert.Equal(new[] { patches[10], patches[11] }, passes[5]); + Assert.Equal(new[] { patches[12], patches[13] }, passes[5]); Assert.Equal(":FOR[MOD2]", passes[6].Name); - Assert.Equal(new[] { patches[12], patches[13] }, passes[6]); + Assert.Equal(new[] { patches[14], patches[15] }, passes[6]); Assert.Equal(":AFTER[MOD2]", passes[7].Name); - Assert.Equal(new[] { patches[14], patches[15] }, passes[7]); + Assert.Equal(new[] { patches[16], patches[17] }, passes[7]); + + Assert.Equal(":LAST[MOD1]", passes[8].Name); + Assert.Equal(new[] { patches[10], patches[11] }, passes[8]); + + Assert.Equal(":LAST[MOD2]", passes[9].Name); + Assert.Equal(new[] { patches[18], patches[19] }, passes[9]); - Assert.Equal(":FINAL", passes[8].Name); - Assert.Equal(new[] { patches[16], patches[17] }, passes[8]); + Assert.Equal(":FINAL", passes[10].Name); + Assert.Equal(new[] { patches[20], patches[21] }, passes[10]); progress.Received(patches.Length).PatchAdded(); } diff --git a/ModuleManagerTests/Patches/PassSpecifiers/LastPassSpecifierTest.cs b/ModuleManagerTests/Patches/PassSpecifiers/LastPassSpecifierTest.cs new file mode 100644 index 00000000..bdf6bee5 --- /dev/null +++ b/ModuleManagerTests/Patches/PassSpecifiers/LastPassSpecifierTest.cs @@ -0,0 +1,58 @@ +using System; +using Xunit; +using NSubstitute; +using ModuleManager; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManagerTests.Patches +{ + public class LastPassSpecifierTest + { + public readonly INeedsChecker needsChecker = Substitute.For(); + public readonly IPatchProgress progress = Substitute.For(); + public readonly LastPassSpecifier passSpecifier; + + public LastPassSpecifierTest() + { + passSpecifier = new LastPassSpecifier("mod1"); + } + + [Fact] + public void TestConstructor__ModNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LastPassSpecifier(null); + }); + + Assert.Equal("mod", ex.ParamName); + } + + [Fact] + public void TestConstructor__ModEmpty() + { + ArgumentException ex = Assert.Throws(delegate + { + new LastPassSpecifier(""); + }); + + Assert.Equal("mod", ex.ParamName); + Assert.Contains("can't be empty", ex.Message); + } + + [Fact] + public void TestCheckNeeds() + { + passSpecifier.CheckNeeds(needsChecker, progress); + + needsChecker.DidNotReceiveWithAnyArgs().CheckNeeds(null); + } + + [Fact] + public void TestDescriptor() + { + Assert.Equal(":LAST[MOD1]", passSpecifier.Descriptor); + } + } +} diff --git a/ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs b/ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs index 07477bb4..fd2ff4ab 100644 --- a/ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs +++ b/ModuleManagerTests/Patches/ProtoPatchBuilderTest.cs @@ -475,6 +475,75 @@ public void TestBuild__After__Case2() Assert.Same(urlConfig, passSpecifier.urlConfig); } + [Fact] + public void TestBuild__Last() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("LAST", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + LastPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + } + + [Fact] + public void TestBuild__Last__Case1() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("Last", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + LastPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + } + + [Fact] + public void TestBuild__Last__Case2() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("last", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + EnsureNoErrors(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + LastPassSpecifier passSpecifier = Assert.IsType(protoPatch.passSpecifier); + Assert.Equal("stuff", passSpecifier.mod); + } + [Fact] public void TestBuild__Final() { @@ -1102,6 +1171,79 @@ public void TestBuild__PassSpecifierOnInsert__After() EnsureNoExceptions(); } + [Fact] + public void TestBuild__NullLast() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("LAST", null, null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :LAST tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__EmptyLast() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("LAST", "", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Copy, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "empty :LAST tag detected: abc/def/NODE"); + EnsureNoExceptions(); + } + + [Fact] + public void TestBuild__MoreThanOnePass__Last() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("FIRST", null, null), + new Tag("LAST", "stuff", null) + )); + + ProtoPatch protoPatch = builder.Build(urlConfig, Command.Copy, tagList); + + progress.Received().Warning(urlConfig, "more than one pass specifier detected, ignoring all but the first: abc/def/NODE"); + progress.DidNotReceiveWithAnyArgs().Error(null, null); + EnsureNoExceptions(); + + Assert.Same(urlConfig, protoPatch.urlConfig); + Assert.Equal(Command.Copy, protoPatch.command); + Assert.Equal("NODE", protoPatch.nodeType); + Assert.Null(protoPatch.nodeName); + Assert.Null(protoPatch.needs); + Assert.Null(protoPatch.has); + Assert.IsType(protoPatch.passSpecifier); + } + + [Fact] + public void TestBuild__PassSpecifierOnInsert__Last() + { + ITagList tagList = Substitute.For(); + tagList.PrimaryTag.Returns(new Tag("NODE", null, null)); + tagList.GetEnumerator().Returns(new ArrayEnumerator( + new Tag("LAST", "mod1", null) + )); + + Assert.Null(builder.Build(urlConfig, Command.Insert, tagList)); + + progress.DidNotReceiveWithAnyArgs().Warning(null, null); + progress.Received().Error(urlConfig, "pass specifier detected on insert node (not a patch): abc/def/NODE"); + EnsureNoExceptions(); + } + [Fact] public void TestBuild__BracketsOnFinal() { From ea89e1a6321aabd8b193969f8a538952b732802a Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Sun, 11 Nov 2018 12:38:00 -0800 Subject: [PATCH 224/342] return iterator rather than array This is only iterated over once, I don't know why it was implemented as an array originally --- ModuleManager/PatchList.cs | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index e600a7c0..69c58537 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -125,34 +125,28 @@ public PatchList(IEnumerable modList, IEnumerable patches, IPatc } } - public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(EnumeratePasses()); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private IPass[] EnumeratePasses() + public IEnumerator GetEnumerator() { - IPass[] result = new IPass[modPasses.Count * 4 + 3]; - - result[0] = firstPatches; - result[1] = legacyPatches; + yield return firstPatches; + yield return legacyPatches; - for (int i = 0; i < modPasses.Count; i++) + foreach (ModPass modPass in modPasses) { - result[i * 3 + 2] = modPasses[i].beforePass; - result[i * 3 + 3] = modPasses[i].forPass; - result[i * 3 + 4] = modPasses[i].afterPass; + yield return modPass.beforePass; + yield return modPass.forPass; + yield return modPass.afterPass; } - for (int i = 0; i < modPasses.Count; i++) + foreach (ModPass modPass in modPasses) { - result[2 + (modPasses.Count * 3) + i] = modPasses[i].lastPass; + yield return modPass.lastPass; } - result[result.Length - 1] = finalPatches; - - return result; + yield return finalPatches; } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + private void EnsureMod(string mod) { if (mod == null) throw new ArgumentNullException(nameof(mod)); From 00216d33dcf53034d6c4e593effd84d67e341c49 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Mon, 12 Nov 2018 19:36:54 +0100 Subject: [PATCH 225/342] v3.1.1 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index a27f4b16..a03f38e4 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.1.0")] +[assembly: AssemblyVersion("3.1.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 6e75d17c8f245e13b8dd7fb5ca271da1e05c9fd1 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 12 Nov 2018 23:37:47 -0800 Subject: [PATCH 226/342] Fix color tag --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 882f8e0c..48af7e51 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -693,7 +693,7 @@ private void StatusUpdate(IPatchProgress progress) status = "ModuleManager: " + progress.Counter.patchedNodes + " patch" + (progress.Counter.patchedNodes != 1 ? "es" : "") + " applied"; if (progress.Counter.warnings > 0) - status += ", found " + progress.Counter.warnings + " warning" + (progress.Counter.warnings != 1 ? "s" : "") + ""; + status += ", found " + progress.Counter.warnings + " warning" + (progress.Counter.warnings != 1 ? "s" : "") + ""; if (progress.Counter.errors > 0) status += ", found " + progress.Counter.errors + " error" + (progress.Counter.errors != 1 ? "s" : "") + ""; From 458b4862eeef6f1d97c5683122e799fbe4e64311 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 24 Nov 2018 23:38:58 -0800 Subject: [PATCH 227/342] Clear filesSha before generating sha Otherwise this ends up getting reused for database reloads then generates a bunch of errors due to duplicate keys and doesn't actually detect changes --- ModuleManager/MMPatchLoader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 882f8e0c..f07755b5 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -436,6 +436,9 @@ private bool IsCacheUpToDate() System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); System.Security.Cryptography.SHA256 filesha = System.Security.Cryptography.SHA256.Create(); UrlDir.UrlFile[] files = GameDatabase.Instance.root.AllConfigFiles.ToArray(); + + filesSha.Clear(); + for (int i = 0; i < files.Length; i++) { // Hash the file path so the checksum change if files are moved From dd851a136149c97acb1630bc74fb5d712985f7e5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 24 Nov 2018 23:42:55 -0800 Subject: [PATCH 228/342] Don't run setup outside of loading screen KSP tries to re-instantiate all addons ona a database reload. It NREs because the loading screen doesn't exist anymore. None of this stuff needs to be run again. --- ModuleManager/ModuleManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 96a40656..454082d6 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -60,6 +60,12 @@ public static void Log(String s) internal void Awake() { + if (LoadingScreen.Instance == null) + { + Destroy(gameObject); + return; + } + totalTime.Start(); // Allow loading the background in the laoding screen From edc09b68454fc1b4c9f1f378fcfc208fb89d8954 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 25 Nov 2018 00:08:10 -0800 Subject: [PATCH 229/342] Move election check up Really none of this stuff should run if it's not the right instance --- ModuleManager/ModuleManager.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 454082d6..c5cbd28e 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -66,6 +66,16 @@ internal void Awake() return; } + // Ensure that only one copy of the service is run per scene change. + if (loadedInScene || !ElectionAndCheck()) + { + Assembly currentAssembly = Assembly.GetExecutingAssembly(); + Log("Multiple copies of current version. Using the first copy. Version: " + + currentAssembly.GetName().Version); + Destroy(gameObject); + return; + } + totalTime.Start(); // Allow loading the background in the laoding screen @@ -87,16 +97,6 @@ internal void Awake() { textPos = Mathf.Min(textPos, text.rectTransform.localPosition.y); } - - // Ensure that only one copy of the service is run per scene change. - if (loadedInScene || !ElectionAndCheck()) - { - Assembly currentAssembly = Assembly.GetExecutingAssembly(); - Log("Multiple copies of current version. Using the first copy. Version: " + - currentAssembly.GetName().Version); - Destroy(gameObject); - return; - } DontDestroyOnLoad(gameObject); Version v = Assembly.GetExecutingAssembly().GetName().Version; From 9f5aa027d834ff26f01fcdce7c6fd3ce00c8b5a8 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 25 Nov 2018 00:08:44 -0800 Subject: [PATCH 230/342] Make MM a once addon Really shouldn't trigger anyway but just to be sure a duplicate doesn't get created --- ModuleManager/ModuleManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index c5cbd28e..869a3079 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -12,7 +12,7 @@ namespace ModuleManager { - [KSPAddon(KSPAddon.Startup.Instantly, false)] + [KSPAddon(KSPAddon.Startup.Instantly, true)] public class ModuleManager : MonoBehaviour { #region state From 7cb640ac6626f126079572e2a9d1a23b710161aa Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 25 Nov 2018 00:09:23 -0800 Subject: [PATCH 231/342] Get rid of unnecessary using --- ModuleManager/ModuleManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 869a3079..9a2b93a2 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -7,7 +7,6 @@ using System.Reflection; using TMPro; using UnityEngine; -using Debug = UnityEngine.Debug; using ModuleManager.Cats; namespace ModuleManager From 4e2af6775cfc1e0ec7b91007863ad3c8c971b6dc Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 25 Nov 2018 11:08:49 -0800 Subject: [PATCH 232/342] v3.1.2 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index a03f38e4..f14fb3ec 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.1.1")] +[assembly: AssemblyVersion("3.1.2")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 357259df4184473489420e91c32bfa5b06bb74be Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 8 Jan 2019 20:00:23 +0100 Subject: [PATCH 233/342] Workaround for 1.6.0 PartDatabase rebuild --- ModuleManager/Fix16.cs | 81 ++++++++++++++++++++++++++++++ ModuleManager/ModuleManager.cs | 7 +++ ModuleManager/ModuleManager.csproj | 1 + 3 files changed, 89 insertions(+) create mode 100644 ModuleManager/Fix16.cs diff --git a/ModuleManager/Fix16.cs b/ModuleManager/Fix16.cs new file mode 100644 index 00000000..ab20e6ae --- /dev/null +++ b/ModuleManager/Fix16.cs @@ -0,0 +1,81 @@ +using System.Collections; + +namespace ModuleManager +{ + class Fix16 : LoadingSystem + { + private void Awake() + { + if (Instance != null) + { + DestroyImmediate(this); + return; + } + + Instance = this; + DontDestroyOnLoad(gameObject); + } + + private static Fix16 Instance { get; set; } + + private bool ready; + + private int count; + + private int current; + + private const int yieldStep = 20; + + public override bool IsReady() + { + return ready; + } + + public override string ProgressTitle() + { + return "Fix 1.6.0 " + current + "/" + count; + } + + public override float ProgressFraction() + { + return (float) current / count; + } + + public override void StartLoad() + { + ready = false; + + count = PartLoader.LoadedPartsList.Count; + + StartCoroutine(DoFix()); + } + + private IEnumerator DoFix() + { + int yieldCounter = 0; + for (current = 0; current < count; current++) + { + AvailablePart avp = PartLoader.LoadedPartsList[current]; + if (avp.partPrefab.dragModel == Part.DragModel.CUBE && !avp.partPrefab.DragCubes.Procedural && + !avp.partPrefab.DragCubes.None && avp.partPrefab.DragCubes.Cubes.Count == 0) + { + DragCubeSystem.Instance.LoadDragCubes(avp.partPrefab); + } + + if (yieldCounter++ >= yieldStep) + { + yieldCounter = 0; + yield return null; + } + } + + ready = true; + yield return null; + } + + public override float LoadWeight() + { + return 0.1f; + } + } +} diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 9a2b93a2..2509855c 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -128,6 +128,13 @@ internal void Awake() int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); list.Insert(gameDatabaseIndex + 1, loader); + + // Workaround for 1.6.0 Editor bug after a PartDatabase rebuild. + if (Versioning.version_major == 1 && Versioning.version_minor == 6 && Versioning.Revision == 0) + { + Fix16 fix16 = aGameObject.AddComponent(); + list.Add(fix16); + } } bool foolsDay = (DateTime.Now.Month == 4 && DateTime.Now.Day == 1); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 1e5efa63..6aef2e69 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -31,6 +31,7 @@ False + From 716cbd1e414c7f75b23d8add94de9c21cd11bc94 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 8 Jan 2019 20:03:07 +0100 Subject: [PATCH 234/342] v3.1.3 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index f14fb3ec..d3094e3a 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.1.2")] +[assembly: AssemblyVersion("3.1.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From b605a7345aed428c1f1d64856329fcdf722c2391 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 25 Nov 2018 23:38:28 -0800 Subject: [PATCH 235/342] Update NuGet packages where available Others require .NET 4 --- ModuleManagerTests/ModuleManagerTests.csproj | 8 ++++---- ModuleManagerTests/packages.config | 4 ++-- TestUtilsTests/TestUtilsTests.csproj | 8 ++++---- TestUtilsTests/packages.config | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 654b7839..234e4441 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -1,7 +1,7 @@  - - + + Debug @@ -116,7 +116,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index dc40245d..1db300e6 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -2,6 +2,6 @@ - - + + \ No newline at end of file diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 1e7d65dd..e59fb6d5 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -1,7 +1,7 @@  - - + + Debug @@ -69,7 +69,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/TestUtilsTests/packages.config b/TestUtilsTests/packages.config index cf3669ce..a0f40e78 100644 --- a/TestUtilsTests/packages.config +++ b/TestUtilsTests/packages.config @@ -1,6 +1,6 @@  - - + + \ No newline at end of file From 29932505aafa752c4a3bb7eb1cd51491118c9fd1 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 25 Nov 2018 23:46:53 -0800 Subject: [PATCH 236/342] Accessory projects on .NET 4.7.1 Visual Studio apparently requires .NET 4 to run tests now. Main project stays on 3.5 since KSP requires that. --- ModuleManagerTests/ModuleManagerTests.csproj | 5 ++++- ModuleManagerTests/packages.config | 4 ++-- TestUtils/TestUtils.csproj | 5 ++++- TestUtilsTests/TestUtilsTests.csproj | 5 ++++- TestUtilsTests/packages.config | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 234e4441..2ca88a71 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -11,10 +11,11 @@ Properties ModuleManagerTests ModuleManagerTests - v3.5 + v4.7.1 512 + true @@ -24,6 +25,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -32,6 +34,7 @@ TRACE prompt 4 + false diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index 1db300e6..9edbb289 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -1,7 +1,7 @@  - + - + \ No newline at end of file diff --git a/TestUtils/TestUtils.csproj b/TestUtils/TestUtils.csproj index 06ab8be0..10073bd0 100644 --- a/TestUtils/TestUtils.csproj +++ b/TestUtils/TestUtils.csproj @@ -9,8 +9,9 @@ Properties TestUtils TestUtils - v3.5 + v4.7.1 512 + true @@ -20,6 +21,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -28,6 +30,7 @@ TRACE prompt 4 + false diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index e59fb6d5..8422b948 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -11,10 +11,11 @@ Properties TestUtilsTests TestUtilsTests - v3.5 + v4.7.1 512 + true @@ -24,6 +25,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -32,6 +34,7 @@ TRACE prompt 4 + false diff --git a/TestUtilsTests/packages.config b/TestUtilsTests/packages.config index a0f40e78..95d3d31b 100644 --- a/TestUtilsTests/packages.config +++ b/TestUtilsTests/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file From 9a9bd677dadd99e360490bb2b110522b3ca3b1bd Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 25 Nov 2018 23:50:06 -0800 Subject: [PATCH 237/342] Update NSubstitute to latest --- ModuleManagerTests/ModuleManagerTests.csproj | 16 ++++++++++++++-- ModuleManagerTests/app.config | 15 +++++++++++++++ ModuleManagerTests/packages.config | 5 ++++- 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 ModuleManagerTests/app.config diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 2ca88a71..722bcfe7 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -38,11 +38,22 @@ - - ..\packages\NSubstitute.2.0.3\lib\net35\NSubstitute.dll + + ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + + + + ..\packages\NSubstitute.3.1.0\lib\net46\NSubstitute.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + @@ -99,6 +110,7 @@ + diff --git a/ModuleManagerTests/app.config b/ModuleManagerTests/app.config new file mode 100644 index 00000000..1d152980 --- /dev/null +++ b/ModuleManagerTests/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index 9edbb289..2c63a510 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -1,6 +1,9 @@  - + + + + From bb858604b6cdd5f1369ce0b7cad8819e355b133c Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 25 Nov 2018 23:58:27 -0800 Subject: [PATCH 238/342] Update Xunit --- ModuleManagerTests/ModuleManagerTests.csproj | 21 ++++++++++++++++++-- ModuleManagerTests/packages.config | 8 +++++++- TestUtilsTests/TestUtilsTests.csproj | 21 ++++++++++++++++++-- TestUtilsTests/packages.config | 8 +++++++- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 722bcfe7..1e179f0c 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -1,5 +1,6 @@  + @@ -59,8 +60,18 @@ - - ..\packages\xunit.1.9.2\lib\net20\xunit.dll + + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll + True + + + ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll @@ -126,6 +137,9 @@ TestUtils + + + @@ -133,5 +147,8 @@ + + + \ No newline at end of file diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index 2c63a510..dc5635be 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -4,7 +4,13 @@ - + + + + + + + \ No newline at end of file diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 8422b948..619e6bb4 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -1,5 +1,6 @@  + @@ -45,8 +46,18 @@ - - ..\packages\xunit.1.9.2\lib\net20\xunit.dll + + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll + True + + + ..\packages\xunit.assert.2.4.1\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.4.1\lib\net452\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.4.1\lib\net452\xunit.execution.desktop.dll @@ -67,6 +78,9 @@ + + + @@ -74,5 +88,8 @@ + + + \ No newline at end of file diff --git a/TestUtilsTests/packages.config b/TestUtilsTests/packages.config index 95d3d31b..bbb4cb82 100644 --- a/TestUtilsTests/packages.config +++ b/TestUtilsTests/packages.config @@ -1,6 +1,12 @@  - + + + + + + + \ No newline at end of file From 0968853237bbf2341f14a9456a2417a982efc1fb Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 26 Nov 2018 00:01:25 -0800 Subject: [PATCH 239/342] Fix Xunit warnings --- ModuleManagerTests/DummyTest.cs | 2 +- ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs | 1 + ModuleManagerTests/PatchExtractorTest.cs | 2 +- ModuleManagerTests/Patches/CopyPatchTest.cs | 2 +- ModuleManagerTests/Patches/EditPatchTest.cs | 2 +- ModuleManagerTests/Patches/PatchCompilerTest.cs | 4 ++-- TestUtilsTests/DummyTest.cs | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ModuleManagerTests/DummyTest.cs b/ModuleManagerTests/DummyTest.cs index d9fc0624..50783efd 100644 --- a/ModuleManagerTests/DummyTest.cs +++ b/ModuleManagerTests/DummyTest.cs @@ -8,7 +8,7 @@ public class DummyTest [Fact] public void PassingTest() { - Assert.Equal(true, true); + Assert.True(true); } } } diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index e82971af..7e4030ad 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -8,6 +8,7 @@ namespace ModuleManagerTests.Extensions { public class ConfigNodeExtensionsTest { + [Fact] public void TestShallowCopyFrom() { ConfigNode fromNode = new TestConfigNode("SOME_NODE") diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index e2fab5c0..a7e2f0a0 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -307,7 +307,7 @@ public void TestExtractPatch__Insert() patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null); - Assert.Equal(1, root.AllConfigs.Count()); + Assert.Single(root.AllConfigs); UrlDir.UrlConfig newUrlConfig = root.AllConfigs.First(); Assert.NotSame(urlConfig, newUrlConfig); Assert.NotSame(urlConfig.config, newUrlConfig.config); diff --git a/ModuleManagerTests/Patches/CopyPatchTest.cs b/ModuleManagerTests/Patches/CopyPatchTest.cs index 70eddd6e..635e5e22 100644 --- a/ModuleManagerTests/Patches/CopyPatchTest.cs +++ b/ModuleManagerTests/Patches/CopyPatchTest.cs @@ -232,7 +232,7 @@ public void TestApply__NameNotChanged() patch.Apply(file, progress, logger); - Assert.Equal(1, file.configs.Count); + Assert.Single(file.configs); Assert.Same(urlConfig, file.configs[0]); AssertNodesEqual(new TestConfigNode("NODE") diff --git a/ModuleManagerTests/Patches/EditPatchTest.cs b/ModuleManagerTests/Patches/EditPatchTest.cs index d0afbc24..e72963f1 100644 --- a/ModuleManagerTests/Patches/EditPatchTest.cs +++ b/ModuleManagerTests/Patches/EditPatchTest.cs @@ -178,7 +178,7 @@ public void TestApply__Loop() patch.Apply(file, progress, logger); - Assert.Equal(1, file.configs.Count); + Assert.Single(file.configs); Assert.NotSame(urlConfig, file.configs[0]); AssertNodesEqual(new TestConfigNode("NODE") { diff --git a/ModuleManagerTests/Patches/PatchCompilerTest.cs b/ModuleManagerTests/Patches/PatchCompilerTest.cs index 4484e61b..5e660876 100644 --- a/ModuleManagerTests/Patches/PatchCompilerTest.cs +++ b/ModuleManagerTests/Patches/PatchCompilerTest.cs @@ -50,7 +50,7 @@ public void TestCompilePatch__Edit() progress.Received().ApplyingUpdate(urlConfig, protoPatch.urlConfig); - Assert.Equal(1, file.configs.Count); + Assert.Single(file.configs); Assert.NotSame(urlConfig, file.configs[0]); AssertNodesEqual(new TestConfigNode("NODE") { @@ -137,7 +137,7 @@ public void TestCompilePatch__Delete() progress.Received().ApplyingDelete(urlConfig, protoPatch.urlConfig); - Assert.Equal(0, file.configs.Count); + Assert.Empty(file.configs); } [Fact] diff --git a/TestUtilsTests/DummyTest.cs b/TestUtilsTests/DummyTest.cs index 28c10410..815d8fb6 100644 --- a/TestUtilsTests/DummyTest.cs +++ b/TestUtilsTests/DummyTest.cs @@ -8,7 +8,7 @@ public class DummyTest [Fact] public void PassingTest() { - Assert.Equal(true, true); + Assert.True(true); } } } From e03db37b1605fe8496b3456f0af5a7a752439442 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 24 Mar 2018 12:09:39 -0700 Subject: [PATCH 240/342] implement stream logger log to a stream --- ModuleManager/Logging/StreamLogger.cs | 69 ++++++ ModuleManager/ModuleManager.csproj | 1 + .../Logging/StreamLoggerTest.cs | 225 ++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 4 files changed, 296 insertions(+) create mode 100644 ModuleManager/Logging/StreamLogger.cs create mode 100644 ModuleManagerTests/Logging/StreamLoggerTest.cs diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs new file mode 100644 index 00000000..4da737e1 --- /dev/null +++ b/ModuleManager/Logging/StreamLogger.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using UnityEngine; + +namespace ModuleManager.Logging +{ + public class StreamLogger : IBasicLogger, IDisposable + { + private readonly Stream stream; + private readonly StreamWriter streamWriter; + private readonly IBasicLogger exceptionLogger; + private bool disposed = false; + + public StreamLogger(Stream stream, IBasicLogger exceptionLogger) + { + this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); + if (!stream.CanWrite) throw new ArgumentException("must be writable", nameof(stream)); + streamWriter = new StreamWriter(stream); + this.exceptionLogger = exceptionLogger ?? throw new ArgumentNullException(nameof(exceptionLogger)); + } + + public void Log(LogType logType, string message) + { + if (disposed) throw new InvalidOperationException("Object has already been disposed"); + try + { + string prefix; + if (logType == LogType.Log) + prefix = "LOG"; + else if (logType == LogType.Warning) + prefix = "WRN"; + else if (logType == LogType.Error) + prefix = "ERR"; + else if (logType == LogType.Assert) + prefix = "AST"; + else if (logType == LogType.Exception) + prefix = "EXC"; + else + prefix = "UNK"; + + streamWriter.WriteLine("[{0} {1}] {2}", prefix, DateTime.Now.ToString(), message); + } + catch (Exception e) + { + exceptionLogger.Exception("Exception while attempting to log to stream", e); + } + } + + public void Exception(string message, Exception exception) + { + Log(LogType.Exception, exception?.ToString() ?? ""); + } + + public void Dispose() + { + try + { + // Flushes and closes the StreamWriter and the underlying stream + streamWriter.Close(); + } + catch(Exception e) + { + exceptionLogger.Exception("Exception while attempting to close stream writer", e); + } + + disposed = true; + } + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 6aef2e69..52efc4e8 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -47,6 +47,7 @@ + diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs new file mode 100644 index 00000000..2b9f8733 --- /dev/null +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -0,0 +1,225 @@ +using System; +using System.IO; +using UnityEngine; +using Xunit; +using NSubstitute; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class StreamLoggerTest + { + [Fact] + public void TestConstructor__StreamNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new StreamLogger(null, Substitute.For()); + }); + + Assert.Equal("stream", ex.ParamName); + } + + [Fact] + public void TestConstructor__CantWrite() + { + using (MemoryStream stream = new MemoryStream(new byte[0], false)) + { + ArgumentException ex = Assert.Throws(delegate + { + new StreamLogger(stream, Substitute.For()); + }); + + Assert.Equal("stream", ex.ParamName); + Assert.Contains("must be writable", ex.Message); + } + } + + [Fact] + public void TestConstructor__ExceptionLoggerNull() + { + using (MemoryStream stream = new MemoryStream(new byte[0], true)) + { + ArgumentNullException ex = Assert.Throws(delegate + { + new StreamLogger(stream, null); + }); + + Assert.Equal("exceptionLogger", ex.ParamName); + } + } + + [Fact] + public void TestLog__AlreadyDisposed() + { + using (MemoryStream stream = new MemoryStream(new byte[0], true)) + { + StreamLogger streamLogger = new StreamLogger(stream, Substitute.For()); + streamLogger.Dispose(); + + InvalidOperationException ex = Assert.Throws(delegate + { + streamLogger.Log(LogType.Log, "a message"); + }); + + Assert.Contains("Object has already been disposed", ex.Message); + } + } + + [Fact] + public void TestLog__Log() + { + IBasicLogger exceptionLogger = Substitute.For(); + byte[] bytes = new byte[50]; + using (MemoryStream stream = new MemoryStream(bytes, true)) + { + using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + { + streamLogger.Log(LogType.Log, "a message"); + } + } + + exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); + + using (MemoryStream stream = new MemoryStream(bytes, false)) + { + using (StreamReader reader = new StreamReader(stream)) + { + string result = reader.ReadToEnd(); + Assert.Contains("[LOG ", result); + Assert.Contains("] a message", result); + } + } + } + + [Fact] + public void TestLog__Assert() + { + IBasicLogger exceptionLogger = Substitute.For(); + byte[] bytes = new byte[50]; + using (MemoryStream stream = new MemoryStream(bytes, true)) + { + using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + { + streamLogger.Log(LogType.Assert, "a message"); + } + } + + exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); + + using (MemoryStream stream = new MemoryStream(bytes, false)) + { + using (StreamReader reader = new StreamReader(stream)) + { + string result = reader.ReadToEnd(); + Assert.Contains("[AST ", result); + Assert.Contains("] a message", result); + } + } + } + + [Fact] + public void TestLog__Warning() + { + IBasicLogger exceptionLogger = Substitute.For(); + byte[] bytes = new byte[50]; + using (MemoryStream stream = new MemoryStream(bytes, true)) + { + using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + { + streamLogger.Log(LogType.Warning, "a message"); + } + } + + exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); + + using (MemoryStream stream = new MemoryStream(bytes, false)) + { + using (StreamReader reader = new StreamReader(stream)) + { + string result = reader.ReadToEnd(); + Assert.Contains("[WRN ", result); + Assert.Contains("] a message", result); + } + } + } + + [Fact] + public void TestLog__Error() + { + IBasicLogger exceptionLogger = Substitute.For(); + byte[] bytes = new byte[50]; + using (MemoryStream stream = new MemoryStream(bytes, true)) + { + using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + { + streamLogger.Log(LogType.Error, "a message"); + } + } + + exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); + + using (MemoryStream stream = new MemoryStream(bytes, false)) + { + using (StreamReader reader = new StreamReader(stream)) + { + string result = reader.ReadToEnd(); + Assert.Contains("[ERR ", result); + Assert.Contains("] a message", result); + } + } + } + + [Fact] + public void TestLog__Exception() + { + IBasicLogger exceptionLogger = Substitute.For(); + byte[] bytes = new byte[50]; + using (MemoryStream stream = new MemoryStream(bytes, true)) + { + using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + { + streamLogger.Log(LogType.Exception, "a message"); + } + } + + exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); + + using (MemoryStream stream = new MemoryStream(bytes, false)) + { + using (StreamReader reader = new StreamReader(stream)) + { + string result = reader.ReadToEnd(); + Assert.Contains("[EXC ", result); + Assert.Contains("] a message", result); + } + } + } + + [Fact] + public void TestLog__Unknown() + { + IBasicLogger exceptionLogger = Substitute.For(); + byte[] bytes = new byte[50]; + using (MemoryStream stream = new MemoryStream(bytes, true)) + { + using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + { + streamLogger.Log((LogType)1000, "a message"); + } + } + + exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); + + using (MemoryStream stream = new MemoryStream(bytes, false)) + { + using (StreamReader reader = new StreamReader(stream)) + { + string result = reader.ReadToEnd(); + Assert.Contains("[UNK ", result); + Assert.Contains("] a message", result); + } + } + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 1e179f0c..6272e879 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -78,6 +78,7 @@ + From 16d88d209d5f1cc5f22061d2e9e065122d1f00a9 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 21 Oct 2018 22:50:28 -0700 Subject: [PATCH 241/342] Unnecessary using --- ModuleManager/Progress/IPatchProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManager/Progress/IPatchProgress.cs b/ModuleManager/Progress/IPatchProgress.cs index fe03995d..4edd07c2 100644 --- a/ModuleManager/Progress/IPatchProgress.cs +++ b/ModuleManager/Progress/IPatchProgress.cs @@ -1,5 +1,4 @@ using System; -using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager.Progress { From 376b71fe5cdee0ea1410bb4d63763ed00bc251c9 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 22 Oct 2018 00:42:28 -0700 Subject: [PATCH 242/342] These are already run by MMPatchLoader No need to explicitly run them again on database reload --- ModuleManager/ModuleManager.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 2509855c..1d20a586 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -334,9 +334,6 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) while (!MMPatchLoader.Instance.IsReady()) yield return null; - PartResourceLibrary.Instance.LoadDefinitions(); - - PartUpgradeManager.Handler.FillUpgrades(); if (dump) OutputAllConfigs(); From e1a2be8b7b7dfab2c1c9f1acc0a96cc6d3696b34 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 22 Oct 2018 22:02:49 -0700 Subject: [PATCH 243/342] Extract post patch Now its own loading system Extract test runner and add tests for it (meta!) --- ModuleManager/MMPatchLoader.cs | 153 +-------------- ModuleManager/ModuleManager.cs | 15 +- ModuleManager/ModuleManager.csproj | 2 + ModuleManager/ModuleManagerPostPatch.cs | 154 +++++++++++++++ ModuleManager/ModuleManagerTestRunner.cs | 75 ++++++++ ModuleManagerTests/InGameTestRunnerTest.cs | 188 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 7 files changed, 432 insertions(+), 156 deletions(-) create mode 100644 ModuleManager/ModuleManagerPostPatch.cs create mode 100644 ModuleManager/ModuleManagerTestRunner.cs create mode 100644 ModuleManagerTests/InGameTestRunnerTest.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index ca782e0c..4df7667e 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -23,8 +23,6 @@ namespace ModuleManager { - public delegate void ModuleManagerPostPatchCallback(); - [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public class MMPatchLoader : LoadingSystem @@ -57,8 +55,6 @@ public class MMPatchLoader : LoadingSystem private string configSha; private Dictionary filesSha = new Dictionary(); - private static readonly List postPatchCallbacks = new List(); - private const float yieldInterval = 1f/30f; // Patch at ~30fps private IBasicLogger logger; @@ -75,7 +71,6 @@ private void Awake() return; } Instance = this; - DontDestroyOnLoad(gameObject); cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); @@ -115,8 +110,7 @@ public override void StartLoad() public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) { - if (!postPatchCallbacks.Contains(callback)) - postPatchCallbacks.Add(callback); + PostPatchLoader.AddPostPatchCallback(callback); } private IEnumerator ProcessPatch() @@ -303,91 +297,6 @@ float updateTimeRemaining() logger.Info(status + "\n" + errors); -#if DEBUG - RunTestCases(); -#endif - - // TODO : Remove if we ever get a way to load sooner - logger.Info("Reloading resources definitions"); - PartResourceLibrary.Instance.LoadDefinitions(); - - logger.Info("Reloading Trait configs"); - GameDatabase.Instance.ExperienceConfigs.LoadTraitConfigs(); - - logger.Info("Reloading Part Upgrades"); - PartUpgradeManager.Handler.FillUpgrades(); - - foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks) - { - try - { - callback(); - } - catch (Exception e) - { - logger.Exception("Exception while running a post patch callback", e); - } - yield return null; - } - yield return null; - - // Call all "public static void ModuleManagerPostLoad()" on all class - foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies()) - { - try - { - foreach (Type type in ass.GetTypes()) - { - MethodInfo method = type.GetMethod("ModuleManagerPostLoad", BindingFlags.Public | BindingFlags.Static); - - if (method != null && method.GetParameters().Length == 0) - { - try - { - logger.Info("Calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()"); - method.Invoke(null, null); - } - catch (Exception e) - { - logger.Exception("Exception while calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()", e); - } - } - } - } - catch (Exception e) - { - logger.Exception("Post run call threw an exception in loading " + ass.FullName, e); - } - } - - yield return null; - - // Call "public void ModuleManagerPostLoad()" on all active MonoBehaviour instance - foreach (MonoBehaviour obj in FindObjectsOfType()) - { - MethodInfo method = obj.GetType().GetMethod("ModuleManagerPostLoad", BindingFlags.Public | BindingFlags.Instance); - - if (method != null && method.GetParameters().Length == 0) - { - try - { - logger.Info("Calling " + obj.GetType().Name + "." + method.Name + "()"); - method.Invoke(obj, null); - } - catch (Exception e) - { - logger.Exception("Exception while calling " + obj.GetType().Name + "." + method.Name + "() :\n", e); - } - } - } - - yield return null; - - if (ModuleManager.dumpPostPatch) - ModuleManager.OutputAllConfigs(); - - yield return null; - patchSw.Stop(); logger.Info("Ran in " + ((float)patchSw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); @@ -1935,66 +1844,6 @@ private static ConfigNode.Value FindValueIn(ConfigNode newNode, string valName, return v; } - private static bool CompareRecursive(ConfigNode expectNode, ConfigNode gotNode) - { - if (expectNode.values.Count != gotNode.values.Count || expectNode.nodes.Count != gotNode.nodes.Count) - return false; - for (int i = 0; i < expectNode.values.Count; ++i) - { - ConfigNode.Value eVal = expectNode.values[i]; - ConfigNode.Value gVal = gotNode.values[i]; - if (eVal.name != gVal.name || eVal.value != gVal.value) - return false; - } - for (int i = 0; i < expectNode.nodes.Count; ++i) - { - ConfigNode eNode = expectNode.nodes[i]; - ConfigNode gNode = gotNode.nodes[i]; - if (!CompareRecursive(eNode, gNode)) - return false; - } - return true; - } - #endregion Config Node Utilities - - #region Tests - - private void RunTestCases() - { - logger.Info("Running tests..."); - - // Do MM testcases - foreach (UrlDir.UrlConfig expect in GameDatabase.Instance.GetConfigs("MMTEST_EXPECT")) - { - // So for each of the expects, we expect all the configs before that node to match exactly. - UrlDir.UrlFile parent = expect.parent; - if (parent.configs.Count != expect.config.CountNodes + 1) - { - logger.Error("Test " + parent.name + " failed as expected number of nodes differs expected:" + - expect.config.CountNodes + " found: " + parent.configs.Count); - for (int i = 0; i < parent.configs.Count; ++i) - logger.Info(parent.configs[i].config.ToString()); - continue; - } - for (int i = 0; i < expect.config.CountNodes; ++i) - { - ConfigNode gotNode = parent.configs[i].config; - ConfigNode expectNode = expect.config.nodes[i]; - if (!CompareRecursive(expectNode, gotNode)) - { - logger.Error("Test " + parent.name + "[" + i + - "] failed as expected output and actual output differ.\nexpected:\n" + expectNode + - "\nActually got:\n" + gotNode); - } - } - - // Purge the tests - parent.configs.Clear(); - } - logger.Info("tests complete."); - } - - #endregion Tests } } diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 1d20a586..8ed242a4 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -121,13 +121,16 @@ internal void Awake() // We could insert ModuleManager after GameDatabase to get it to run there // and SaveGameFixer after PartLoader. + int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); + GameObject aGameObject = new GameObject("ModuleManager"); - MMPatchLoader loader = aGameObject.AddComponent(); + DontDestroyOnLoad(aGameObject); - Log(string.Format("Adding ModuleManager to the loading screen {0}", list.Count)); + Log(string.Format("Adding patch loader to the loading screen {0}", list.Count)); + list.Insert(gameDatabaseIndex + 1, aGameObject.AddComponent()); - int gameDatabaseIndex = list.FindIndex(s => s is GameDatabase); - list.Insert(gameDatabaseIndex + 1, loader); + Log(string.Format("Adding post patch to the loading screen {0}", list.Count)); + list.Insert(gameDatabaseIndex + 2, aGameObject.AddComponent()); // Workaround for 1.6.0 Editor bug after a PartDatabase rebuild. if (Versioning.version_major == 1 && Versioning.version_minor == 6 && Versioning.Revision == 0) @@ -334,6 +337,10 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) while (!MMPatchLoader.Instance.IsReady()) yield return null; + PostPatchLoader.Instance.StartLoad(); + + while (!PostPatchLoader.Instance.IsReady()) + yield return null; if (dump) OutputAllConfigs(); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 52efc4e8..de9a3e6b 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -58,6 +58,8 @@ + + diff --git a/ModuleManager/ModuleManagerPostPatch.cs b/ModuleManager/ModuleManagerPostPatch.cs new file mode 100644 index 00000000..1f4da965 --- /dev/null +++ b/ModuleManager/ModuleManagerPostPatch.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using UnityEngine; +using ModuleManager.Extensions; +using ModuleManager.Logging; + +namespace ModuleManager +{ + public delegate void ModuleManagerPostPatchCallback(); + + public class PostPatchLoader : LoadingSystem + { + public static PostPatchLoader Instance { get; private set; } + + private static readonly List postPatchCallbacks = new List(); + + private readonly IBasicLogger logger = new ModLogger("ModuleManager", new UnityLogger(UnityEngine.Debug.unityLogger)); + + private bool ready = false; + + public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) + { + if (!postPatchCallbacks.Contains(callback)) + postPatchCallbacks.Add(callback); + } + + private void Awake() + { + if (Instance != null) + { + Destroy(this); + return; + } + Instance = this; + } + + public override bool IsReady() => ready; + + public override float LoadWeight() => 0; + + public override float ProgressFraction() => 1; + + public override string ProgressTitle() => "ModuleManager : post patch"; + + public override void StartLoad() + { + ready = false; + StartCoroutine(Run()); + } + + private IEnumerator Run() + { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + +#if DEBUG + InGameTestRunner testRunner = new InGameTestRunner(logger); + testRunner.RunTestCases(GameDatabase.Instance.root); +#endif + + yield return null; + + logger.Info("Reloading resources definitions"); + PartResourceLibrary.Instance.LoadDefinitions(); + + logger.Info("Reloading Trait configs"); + GameDatabase.Instance.ExperienceConfigs.LoadTraitConfigs(); + + logger.Info("Reloading Part Upgrades"); + PartUpgradeManager.Handler.FillUpgrades(); + + yield return null; + + logger.Info("Running post patch callbacks"); + + foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks) + { + try + { + callback(); + } + catch (Exception e) + { + logger.Exception("Exception while running a post patch callback", e); + } + yield return null; + } + yield return null; + + // Call all "public static void ModuleManagerPostLoad()" on all class + foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + foreach (Type type in ass.GetTypes()) + { + MethodInfo method = type.GetMethod("ModuleManagerPostLoad", BindingFlags.Public | BindingFlags.Static); + + if (method != null && method.GetParameters().Length == 0) + { + try + { + logger.Info("Calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()"); + method.Invoke(null, null); + } + catch (Exception e) + { + logger.Exception("Exception while calling " + ass.GetName().Name + "." + type.Name + "." + method.Name + "()", e); + } + } + } + } + catch (Exception e) + { + logger.Exception("Post run call threw an exception in loading " + ass.FullName, e); + } + } + + yield return null; + + // Call "public void ModuleManagerPostLoad()" on all active MonoBehaviour instance + foreach (MonoBehaviour obj in FindObjectsOfType()) + { + MethodInfo method = obj.GetType().GetMethod("ModuleManagerPostLoad", BindingFlags.Public | BindingFlags.Instance); + + if (method != null && method.GetParameters().Length == 0) + { + try + { + logger.Info("Calling " + obj.GetType().Name + "." + method.Name + "()"); + method.Invoke(obj, null); + } + catch (Exception e) + { + logger.Exception("Exception while calling " + obj.GetType().Name + "." + method.Name + "() :\n", e); + } + } + } + + yield return null; + + if (ModuleManager.dumpPostPatch) + ModuleManager.OutputAllConfigs(); + + stopwatch.Stop(); + logger.Info("Post patch ran in " + ((float)stopwatch.ElapsedMilliseconds / 1000).ToString("F3") + "s"); + + ready = true; + } + } +} diff --git a/ModuleManager/ModuleManagerTestRunner.cs b/ModuleManager/ModuleManagerTestRunner.cs new file mode 100644 index 00000000..2f90a453 --- /dev/null +++ b/ModuleManager/ModuleManagerTestRunner.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ModuleManager.Extensions; +using ModuleManager.Logging; + +namespace ModuleManager +{ + public class InGameTestRunner + { + private readonly IBasicLogger logger; + + public InGameTestRunner(IBasicLogger logger) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public void RunTestCases(UrlDir gameDatabaseRoot) + { + if (gameDatabaseRoot == null) throw new ArgumentNullException(nameof(gameDatabaseRoot)); + logger.Info("Running tests..."); + + foreach (UrlDir.UrlConfig expect in gameDatabaseRoot.GetConfigs("MMTEST_EXPECT")) + { + // So for each of the expects, we expect all the configs before that node to match exactly. + UrlDir.UrlFile parent = expect.parent; + if (parent.configs.Count != expect.config.CountNodes + 1) + { + logger.Error("Test " + parent.name + " failed as expected number of nodes differs expected: " + + expect.config.CountNodes + " found: " + (parent.configs.Count - 1)); + for (int i = 0; i < parent.configs.Count; ++i) + logger.Info(parent.configs[i].config.ToString()); + continue; + } + for (int i = 0; i < expect.config.CountNodes; ++i) + { + ConfigNode gotNode = parent.configs[i].config; + ConfigNode expectNode = expect.config.nodes[i]; + if (!CompareRecursive(expectNode, gotNode)) + { + logger.Error("Test " + parent.name + "[" + i + + "] failed as expected output and actual output differ.\nexpected:\n" + expectNode + + "\nActually got:\n" + gotNode); + } + } + + // Purge the tests + parent.configs.Clear(); + } + logger.Info("tests complete."); + } + + private static bool CompareRecursive(ConfigNode expectNode, ConfigNode gotNode) + { + if (expectNode.values.Count != gotNode.values.Count || expectNode.nodes.Count != gotNode.nodes.Count) + return false; + for (int i = 0; i < expectNode.values.Count; ++i) + { + ConfigNode.Value eVal = expectNode.values[i]; + ConfigNode.Value gVal = gotNode.values[i]; + if (eVal.name != gVal.name || eVal.value != gVal.value) + return false; + } + for (int i = 0; i < expectNode.nodes.Count; ++i) + { + ConfigNode eNode = expectNode.nodes[i]; + ConfigNode gNode = gotNode.nodes[i]; + if (!CompareRecursive(eNode, gNode)) + return false; + } + return true; + } + } +} diff --git a/ModuleManagerTests/InGameTestRunnerTest.cs b/ModuleManagerTests/InGameTestRunnerTest.cs new file mode 100644 index 00000000..cd860bc2 --- /dev/null +++ b/ModuleManagerTests/InGameTestRunnerTest.cs @@ -0,0 +1,188 @@ +using System; +using System.Linq; +using Xunit; +using NSubstitute; +using UnityEngine; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; + +namespace ModuleManagerTests +{ + public class InGameTestRunnerTest + { + private readonly IBasicLogger logger; + private readonly UrlDir databaseRoot; + private readonly InGameTestRunner testRunner; + + public InGameTestRunnerTest() + { + logger = Substitute.For(); + databaseRoot = UrlBuilder.CreateRoot(); + testRunner = new InGameTestRunner(logger); + } + + [Fact] + public void TestConstructor__LoggerNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new InGameTestRunner(null); + }); + + Assert.Equal("logger", ex.ParamName); + } + + [Fact] + public void TestRunTestCases__DatabaseRootNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + testRunner.RunTestCases(null); + }); + + Assert.Equal("gameDatabaseRoot", ex.ParamName); + } + + [Fact] + public void TestRunTestCases__WrongNumberOfNodes() + { + UrlDir.UrlFile file1 = UrlBuilder.CreateFile("abc/blah1.cfg", databaseRoot); + + // Call CreateCopy otherwise XUnit sees that it's an IEnumerable and attempts to compare by enumeration + ConfigNode testNode1 = new TestConfigNode("NODE1") + { + { "key1", "value1" }, + }.CreateCopy(); + + ConfigNode testNode2 = new ConfigNode("NODE2"); + + ConfigNode expectNode = new TestConfigNode("MMTEST_EXPECT") + { + new TestConfigNode("NODE1") + { + { "key1", "value1" }, + }, + }.CreateCopy(); + + UrlBuilder.CreateConfig(testNode1, file1); + UrlBuilder.CreateConfig(testNode2, file1); + UrlBuilder.CreateConfig(expectNode, file1); + + testRunner.RunTestCases(databaseRoot); + + Received.InOrder(delegate + { + logger.Log(LogType.Log, "Running tests..."); + logger.Log(LogType.Error, $"Test blah1 failed as expected number of nodes differs expected: 1 found: 2"); + logger.Log(LogType.Log, testNode1.ToString()); + logger.Log(LogType.Log, testNode2.ToString()); + logger.Log(LogType.Log, expectNode.ToString()); + logger.Log(LogType.Log, "tests complete."); + }); + + Assert.Equal(3, file1.configs.Count); + Assert.Equal(testNode1, file1.configs[0].config); + Assert.Equal(testNode2, file1.configs[1].config); + Assert.Equal(expectNode, file1.configs[2].config); + } + + [Fact] + public void TestRunTestCases__AllPassing() + { + UrlDir.UrlFile file1 = UrlBuilder.CreateFile("abc/blah1.cfg", databaseRoot); + UrlDir.UrlFile file2 = UrlBuilder.CreateFile("abc/blah2.cfg", databaseRoot); + + ConfigNode testNode1 = new TestConfigNode("NODE1") + { + { "key1", "value1" }, + { "key2", "value2" }, + new TestConfigNode("NODE2") + { + { "key3", "value3" }, + }, + }; + + ConfigNode testNode2 = new TestConfigNode("NODE3") + { + { "key4", "value4" }, + }; + + ConfigNode testNode3 = new TestConfigNode("NODE4") + { + { "key5", "value5" }, + }; + + UrlBuilder.CreateConfig(testNode1, file1); + UrlBuilder.CreateConfig(testNode2, file1); + UrlBuilder.CreateConfig(new TestConfigNode("MMTEST_EXPECT") + { + testNode1.CreateCopy(), + testNode2.CreateCopy(), + }, file1); + + UrlBuilder.CreateConfig(testNode3, file2); + UrlBuilder.CreateConfig(new TestConfigNode("MMTEST_EXPECT") + { + testNode3.CreateCopy(), + }, file2); + + testRunner.RunTestCases(databaseRoot); + + Received.InOrder(delegate + { + logger.Log(LogType.Log, "Running tests..."); + logger.Log(LogType.Log, "tests complete."); + }); + + logger.DidNotReceive().Log(LogType.Error, Arg.Any()); + + Assert.Empty(file1.configs); + Assert.Empty(file2.configs); + } + + [Fact] + public void TestRunTestCases__Failure() + { + UrlDir.UrlFile file1 = UrlBuilder.CreateFile("abc/blah1.cfg", databaseRoot); + + ConfigNode testNode1 = new TestConfigNode("NODE1") + { + { "key1", "value1" }, + { "key2", "value2" }, + new TestConfigNode("NODE2") + { + { "key3", "value3" }, + }, + }; + + ConfigNode expectNode1 = new TestConfigNode("NODE1") + { + { "key1", "value1" }, + { "key2", "value2" }, + new TestConfigNode("NODE2") + { + { "key4", "value3" }, + }, + }; + + UrlBuilder.CreateConfig(testNode1, file1); + UrlBuilder.CreateConfig(new TestConfigNode("MMTEST_EXPECT") + { + expectNode1, + }, file1); + + testRunner.RunTestCases(databaseRoot); + + Received.InOrder(delegate + { + logger.Log(LogType.Log, "Running tests..."); + logger.Log(LogType.Error, $"Test blah1[0] failed as expected output and actual output differ.\nexpected:\n{expectNode1}\nActually got:\n{testNode1}"); + logger.Log(LogType.Log, "tests complete."); + }); + + + Assert.Empty(file1.configs); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 6272e879..59998d7b 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -78,6 +78,7 @@ + From 48df502d6d38721788489652d57cd83d1d16a8dd Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 29 Oct 2018 22:44:31 -0700 Subject: [PATCH 244/342] Operate on a copy of the game database then apply * Insert nodes are now patches. They don't support MM syntax yet (just applied directly) but that could be added * ProtoUrlConfig identifies a UrlFile and node without the expectation that the UrlFile knows about the node (turned into a real UrlConfig at the end) * Intermedate state of the game database is now a linked list of nodes --- ModuleManager/MMPatchLoader.cs | 90 ++++----- ModuleManager/ModuleManager.csproj | 2 + ModuleManager/PatchApplier.cs | 18 +- ModuleManager/PatchContext.cs | 7 +- ModuleManager/PatchExtractor.cs | 21 +-- ModuleManager/PatchList.cs | 8 +- ModuleManager/Patches/CopyPatch.cs | 27 ++- ModuleManager/Patches/DeletePatch.cs | 25 ++- ModuleManager/Patches/EditPatch.cs | 25 +-- ModuleManager/Patches/IPatch.cs | 4 +- ModuleManager/Patches/InsertPatch.cs | 34 ++++ ModuleManager/Patches/PatchCompiler.cs | 11 +- ModuleManager/Progress/IPatchProgress.cs | 7 +- ModuleManager/Progress/PatchProgress.cs | 18 +- ModuleManager/ProtoUrlConfig.cs | 34 ++++ ModuleManagerTests/MMPatchLoaderTest.cs | 10 +- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/PatchApplierTest.cs | 47 ++--- ModuleManagerTests/PatchExtractorTest.cs | 79 -------- ModuleManagerTests/PatchListTest.cs | 81 ++++---- ModuleManagerTests/Patches/CopyPatchTest.cs | 142 +++++++++----- ModuleManagerTests/Patches/DeletePatchTest.cs | 47 +++-- ModuleManagerTests/Patches/EditPatchTest.cs | 100 ++++++---- ModuleManagerTests/Patches/InsertPatchTest.cs | 173 ++++++++++++++++++ .../Patches/PatchCompilerTest.cs | 93 ++++++++-- .../Progress/PatchProgressTest.cs | 40 ++-- 26 files changed, 732 insertions(+), 412 deletions(-) create mode 100644 ModuleManager/Patches/InsertPatch.cs create mode 100644 ModuleManager/ProtoUrlConfig.cs create mode 100644 ModuleManagerTests/Patches/InsertPatchTest.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 4df7667e..fee3b2bd 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -167,9 +167,9 @@ private IEnumerator ProcessPatch() PatchExtractor extractor = new PatchExtractor(progress, logger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler); // Have to convert to an array because we will be removing patches - UrlDir.UrlConfig[] allConfigs = GameDatabase.Instance.root.AllConfigs.ToArray(); - IEnumerable extractedPatches = allConfigs.Select(urlConfig => extractor.ExtractPatch(urlConfig)); - PatchList patchList = new PatchList(mods, extractedPatches.Where(patch => patch != null), progress); + IEnumerable extractedPatches = + GameDatabase.Instance.root.AllConfigs.Select(urlConfig => extractor.ExtractPatch(urlConfig)).Where(patch => patch != null); + PatchList patchList = new PatchList(mods, extractedPatches, progress); #endregion @@ -187,9 +187,11 @@ private IEnumerator ProcessPatch() logger.Info("Starting patch thread"); + IEnumerable databaseConfigs = null; + ITaskStatus patchThread = BackgroundTask.Start(delegate { - applier.ApplyPatches(GameDatabase.Instance.root.AllConfigFiles.ToArray(), patchList); + databaseConfigs = applier.ApplyPatches(patchList); }); float nextYield = Time.realtimeSinceStartup + yieldInterval; @@ -236,6 +238,22 @@ float updateTimeRemaining() FatalErrorHandler.HandleFatalError("The patch runner threw an exception"); yield break; } + if (databaseConfigs == null) + { + progress.Error("The patcher returned a null collection of configs"); + FatalErrorHandler.HandleFatalError("The patcher returned a null collection of configs"); + yield break; + } + + foreach (UrlDir.UrlFile file in GameDatabase.Instance.root.AllConfigFiles) + { + file.configs.Clear(); + } + + foreach (IProtoUrlConfig protoConfig in databaseConfigs) + { + protoConfig.UrlFile.AddConfig(protoConfig.Node); + } logger.Info("Done patching"); yield return null; @@ -1219,28 +1237,24 @@ private static ConfigNode RecurseNodeSearch(string path, NodeStack nodeStack, Pa // @XXXXX if (root) { - IEnumerable urlConfigs = context.databaseRoot.GetConfigs(nodeType); - if (!urlConfigs.Any()) + bool foundNodeType = false; + foreach (IProtoUrlConfig urlConfig in context.databaseConfigs) { - context.logger.Warning("Can't find nodeType:" + nodeType); - return null; - } + ConfigNode node = urlConfig.Node; - if (nodeName == null) - { - nodeStack = new NodeStack(urlConfigs.First().config); - } - else - { - foreach (UrlDir.UrlConfig url in urlConfigs) + if (node.name != nodeType) continue; + + foundNodeType = true; + + if (nodeName == null || (node.GetValue("name") is string testNodeName && WildcardMatch(testNodeName, nodeName))) { - if (url.config.HasValue("name") && WildcardMatch(url.config.GetValue("name"), nodeName)) - { - nodeStack = new NodeStack(url.config); - break; - } + nodeStack = new NodeStack(node); + break; } } + + if (!foundNodeType) context.logger.Warning("Can't find nodeType:" + nodeType); + if (nodeStack == null) return null; } else { @@ -1305,7 +1319,6 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod string subName = path.Substring(1, nextSep - 1); string nodeType, nodeName; - UrlDir.UrlConfig target = null; if (subName.Contains("[")) { @@ -1317,32 +1330,27 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod { // @NODETYPE/ nodeType = subName; - nodeName = string.Empty; + nodeName = null; } - IEnumerable urlConfigs = context.databaseRoot.GetConfigs(nodeType); - if (!urlConfigs.Any()) + bool foundNodeType = false; + foreach (IProtoUrlConfig urlConfig in context.databaseConfigs) { - context.logger.Warning("Can't find nodeType:" + nodeType); - return null; - } + ConfigNode node = urlConfig.Node; - if (nodeName == string.Empty) - { - target = urlConfigs.First(); - } - else - { - foreach (UrlDir.UrlConfig url in urlConfigs) + if (node.name != nodeType) continue; + + foundNodeType = true; + + if (nodeName == null || (node.GetValue("name") is string testNodeName && WildcardMatch(testNodeName, nodeName))) { - if (url.config.HasValue("name") && WildcardMatch(url.config.GetValue("name"), nodeName)) - { - target = url; - break; - } + return RecurseVariableSearch(path.Substring(nextSep + 1), new NodeStack(node), context); } } - return target != null ? RecurseVariableSearch(path.Substring(nextSep + 1), new NodeStack(target.config), context) : null; + + if (!foundNodeType) context.logger.Warning("Can't find nodeType:" + nodeType); + + return null; } if (path.StartsWith("../")) { diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index de9a3e6b..6de340a4 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -67,6 +67,7 @@ + @@ -90,6 +91,7 @@ + diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 83440d70..72e4fbd0 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using ModuleManager.Logging; using ModuleManager.Extensions; using ModuleManager.Patches; @@ -21,17 +20,21 @@ public PatchApplier(IPatchProgress progress, IBasicLogger logger) this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public void ApplyPatches(IEnumerable configFiles, IEnumerable patches) + public IEnumerable ApplyPatches(IEnumerable patches) { - if (configFiles == null) throw new ArgumentNullException(nameof(configFiles)); if (patches == null) throw new ArgumentNullException(nameof(patches)); + + LinkedList databaseConfigs = new LinkedList(); + foreach (IPass pass in patches) { - ApplyPatches(configFiles, pass); + ApplyPatches(databaseConfigs, pass); } + + return databaseConfigs; } - private void ApplyPatches(IEnumerable configFiles, IPass pass) + private void ApplyPatches(LinkedList databaseConfigs, IPass pass) { logger.Info(pass.Name + " pass"); Activity = "ModuleManager " + pass.Name; @@ -40,10 +43,7 @@ private void ApplyPatches(IEnumerable configFiles, IPass pass) { try { - foreach (UrlDir.UrlFile file in configFiles) - { - patch.Apply(file, progress, logger); - } + patch.Apply(databaseConfigs, progress, logger); progress.PatchApplied(); } catch (Exception e) diff --git a/ModuleManager/PatchContext.cs b/ModuleManager/PatchContext.cs index ac3b7200..09e2f441 100644 --- a/ModuleManager/PatchContext.cs +++ b/ModuleManager/PatchContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using ModuleManager.Logging; using ModuleManager.Progress; @@ -7,14 +8,14 @@ namespace ModuleManager public struct PatchContext { public readonly UrlDir.UrlConfig patchUrl; - public readonly UrlDir databaseRoot; + public readonly IEnumerable databaseConfigs; public readonly IBasicLogger logger; public readonly IPatchProgress progress; - public PatchContext(UrlDir.UrlConfig patchUrl, UrlDir databaseRoot, IBasicLogger logger, IPatchProgress progress) + public PatchContext(UrlDir.UrlConfig patchUrl, IEnumerable databaseConfigs, IBasicLogger logger, IPatchProgress progress) { this.patchUrl = patchUrl; - this.databaseRoot = databaseRoot; + this.databaseConfigs = databaseConfigs; this.logger = logger; this.progress = progress; } diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index 3306014d..cf476a64 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -39,9 +39,6 @@ public IPatch ExtractPatch(UrlDir.UrlConfig urlConfig) try { - int index = urlConfig.parent.configs.IndexOf(urlConfig); - urlConfig.parent.configs.RemoveAt(index); - if (!urlConfig.type.IsBracketBalanced()) { progress.Error(urlConfig, "Error - node name does not have balanced brackets (or a space - if so replace with ?):\n" + urlConfig.SafeUrl()); @@ -103,21 +100,9 @@ public IPatch ExtractPatch(UrlDir.UrlConfig urlConfig) { return null; } - - if (command == Command.Insert) - { - ConfigNode newNode = urlConfig.config.DeepCopy(); - newNode.name = protoPatch.nodeType; - newNode.id = urlConfig.config.id; - needsChecker.CheckNeedsRecursive(newNode, urlConfig); - urlConfig.parent.configs.Insert(index, new UrlDir.UrlConfig(urlConfig.parent, newNode)); - return null; - } - else - { - needsChecker.CheckNeedsRecursive(urlConfig.config, urlConfig); - return patchCompiler.CompilePatch(protoPatch); - } + + needsChecker.CheckNeedsRecursive(urlConfig.config, urlConfig); + return patchCompiler.CompilePatch(protoPatch); } catch(Exception e) { diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index 69c58537..892b6843 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -70,6 +70,7 @@ public ModPassCollection(IEnumerable modList) IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } + private readonly Pass insertPatches = new Pass(":INSERT (initial)"); private readonly Pass firstPatches = new Pass(":FIRST"); private readonly Pass legacyPatches = new Pass(":LEGACY (default)"); private readonly Pass finalPatches = new Pass(":FINAL"); @@ -84,7 +85,11 @@ public PatchList(IEnumerable modList, IEnumerable patches, IPatc foreach (IPatch patch in patches) { - if (patch.PassSpecifier is FirstPassSpecifier) + if (patch.PassSpecifier is InsertPassSpecifier) + { + insertPatches.Add(patch); + } + else if (patch.PassSpecifier is FirstPassSpecifier) { firstPatches.Add(patch); } @@ -127,6 +132,7 @@ public PatchList(IEnumerable modList, IEnumerable patches, IPatc public IEnumerator GetEnumerator() { + yield return insertPatches; yield return firstPatches; yield return legacyPatches; diff --git a/ModuleManager/Patches/CopyPatch.cs b/ModuleManager/Patches/CopyPatch.cs index b94ee353..05241973 100644 --- a/ModuleManager/Patches/CopyPatch.cs +++ b/ModuleManager/Patches/CopyPatch.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NodeStack = ModuleManager.Collections.ImmutableStack; using ModuleManager.Extensions; using ModuleManager.Logging; @@ -20,37 +21,35 @@ public CopyPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpec PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier)); } - public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger) + public void Apply(LinkedList databaseConfigs, IPatchProgress progress, IBasicLogger logger) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (databaseConfigs == null) throw new ArgumentNullException(nameof(databaseConfigs)); if (progress == null) throw new ArgumentNullException(nameof(progress)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - PatchContext context = new PatchContext(UrlConfig, file.root, logger, progress); + PatchContext context = new PatchContext(UrlConfig, databaseConfigs, logger, progress); - // Avoid checking the new configs we are creating - int count = file.configs.Count; - for (int i = 0; i < count; i++) + for (LinkedListNode listNode = databaseConfigs.First; listNode != null; listNode = listNode.Next) { - UrlDir.UrlConfig url = file.configs[i]; + IProtoUrlConfig protoConfig = listNode.Value; try { - if (!NodeMatcher.IsMatch(url.config)) continue; + if (!NodeMatcher.IsMatch(protoConfig.Node)) continue; - ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(url.config), UrlConfig.config, context); - if (url.config.HasValue("name") && url.config.GetValue("name") == clone.GetValue("name")) + ConfigNode clone = MMPatchLoader.ModifyNode(new NodeStack(protoConfig.Node), UrlConfig.config, context); + if (protoConfig.Node.GetValue("name") is string name && name == clone.GetValue("name")) { - progress.Error(UrlConfig, $"Error - when applying copy {UrlConfig.SafeUrl()} to {url.SafeUrl()} - the copy needs to have a different name than the parent (use @name = xxx)"); + progress.Error(UrlConfig, $"Error - when applying copy {UrlConfig.SafeUrl()} to {protoConfig.FullUrl} - the copy needs to have a different name than the parent (use @name = xxx)"); } else { - progress.ApplyingCopy(url, UrlConfig); - file.AddConfig(clone); + progress.ApplyingCopy(protoConfig, UrlConfig); + listNode = databaseConfigs.AddAfter(listNode, new ProtoUrlConfig(protoConfig.UrlFile, clone)); } } catch (Exception ex) { - progress.Exception(UrlConfig, $"Exception while applying copy {UrlConfig.SafeUrl()} to {url.SafeUrl()}", ex); + progress.Exception(UrlConfig, $"Exception while applying copy {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}", ex); } } } diff --git a/ModuleManager/Patches/DeletePatch.cs b/ModuleManager/Patches/DeletePatch.cs index 6d8762f9..fd465c9a 100644 --- a/ModuleManager/Patches/DeletePatch.cs +++ b/ModuleManager/Patches/DeletePatch.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using ModuleManager.Extensions; using ModuleManager.Logging; using ModuleManager.Patches.PassSpecifiers; @@ -19,31 +20,29 @@ public DeletePatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSp PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier)); } - public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger) + public void Apply(LinkedList databaseConfigs, IPatchProgress progress, IBasicLogger logger) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (databaseConfigs == null) throw new ArgumentNullException(nameof(databaseConfigs)); if (progress == null) throw new ArgumentNullException(nameof(progress)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - int i = 0; - while (i < file.configs.Count) + LinkedListNode currentNode = databaseConfigs.First; + while (currentNode != null) { - UrlDir.UrlConfig url = file.configs[i]; + IProtoUrlConfig protoConfig = currentNode.Value; try { - if (NodeMatcher.IsMatch(url.config)) + LinkedListNode nextNode = currentNode.Next; + if (NodeMatcher.IsMatch(protoConfig.Node)) { - progress.ApplyingDelete(url, UrlConfig); - file.configs.RemoveAt(i); - } - else - { - i++; + progress.ApplyingDelete(protoConfig, UrlConfig); + databaseConfigs.Remove(currentNode); } + currentNode = nextNode; } catch (Exception ex) { - progress.Exception(UrlConfig, $"Exception while applying delete {UrlConfig.SafeUrl()} to {url.SafeUrl()}", ex); + progress.Exception(UrlConfig, $"Exception while applying delete {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}", ex); } } } diff --git a/ModuleManager/Patches/EditPatch.cs b/ModuleManager/Patches/EditPatch.cs index b9358fdb..501144e9 100644 --- a/ModuleManager/Patches/EditPatch.cs +++ b/ModuleManager/Patches/EditPatch.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using NodeStack = ModuleManager.Collections.ImmutableStack; using ModuleManager.Extensions; using ModuleManager.Logging; @@ -24,32 +25,32 @@ public EditPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpec loop = urlConfig.config.HasNode("MM_PATCH_LOOP"); } - public void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger) + public void Apply(LinkedList databaseConfigs, IPatchProgress progress, IBasicLogger logger) { - if (file == null) throw new ArgumentNullException(nameof(file)); + if (databaseConfigs == null) throw new ArgumentNullException(nameof(databaseConfigs)); if (progress == null) throw new ArgumentNullException(nameof(progress)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - PatchContext context = new PatchContext(UrlConfig, file.root, logger, progress); - for (int i = 0; i < file.configs.Count; i++) + PatchContext context = new PatchContext(UrlConfig, databaseConfigs, logger, progress); + for (LinkedListNode listNode = databaseConfigs.First; listNode != null; listNode = listNode.Next) { - UrlDir.UrlConfig urlConfig = file.configs[i]; + IProtoUrlConfig protoConfig = listNode.Value; try { - if (!NodeMatcher.IsMatch(urlConfig.config)) continue; - if (loop) logger.Info($"Looping on {UrlConfig.SafeUrl()} to {urlConfig.SafeUrl()}"); + if (!NodeMatcher.IsMatch(protoConfig.Node)) continue; + if (loop) logger.Info($"Looping on {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}"); do { - progress.ApplyingUpdate(urlConfig, UrlConfig); - file.configs[i] = urlConfig = new UrlDir.UrlConfig(file, MMPatchLoader.ModifyNode(new NodeStack(urlConfig.config), UrlConfig.config, context)); - } while (loop && NodeMatcher.IsMatch(urlConfig.config)); + progress.ApplyingUpdate(protoConfig, UrlConfig); + listNode.Value = protoConfig = new ProtoUrlConfig(protoConfig.UrlFile, MMPatchLoader.ModifyNode(new NodeStack(protoConfig.Node), UrlConfig.config, context)); + } while (loop && NodeMatcher.IsMatch(protoConfig.Node)); - if (loop) file.configs[i].config.RemoveNodes("MM_PATCH_LOOP"); + if (loop) protoConfig.Node.RemoveNodes("MM_PATCH_LOOP"); } catch (Exception ex) { - progress.Exception(UrlConfig, $"Exception while applying update {UrlConfig.SafeUrl()} to {urlConfig.SafeUrl()}", ex); + progress.Exception(UrlConfig, $"Exception while applying update {UrlConfig.SafeUrl()} to {protoConfig.FullUrl}", ex); } } } diff --git a/ModuleManager/Patches/IPatch.cs b/ModuleManager/Patches/IPatch.cs index 57920050..032ce6e1 100644 --- a/ModuleManager/Patches/IPatch.cs +++ b/ModuleManager/Patches/IPatch.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using ModuleManager.Logging; using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; @@ -8,8 +9,7 @@ namespace ModuleManager.Patches public interface IPatch { UrlDir.UrlConfig UrlConfig { get; } - INodeMatcher NodeMatcher { get; } IPassSpecifier PassSpecifier { get; } - void Apply(UrlDir.UrlFile file, IPatchProgress progress, IBasicLogger logger); + void Apply(LinkedList configs, IPatchProgress progress, IBasicLogger logger); } } diff --git a/ModuleManager/Patches/InsertPatch.cs b/ModuleManager/Patches/InsertPatch.cs new file mode 100644 index 00000000..9f3c8a50 --- /dev/null +++ b/ModuleManager/Patches/InsertPatch.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + +namespace ModuleManager.Patches +{ + public class InsertPatch : IPatch + { + public UrlDir.UrlConfig UrlConfig { get; } + public string NodeType { get; } + public IPassSpecifier PassSpecifier { get; } + + public InsertPatch(UrlDir.UrlConfig urlConfig, string nodeType, IPassSpecifier passSpecifier) + { + UrlConfig = urlConfig ?? throw new ArgumentNullException(nameof(urlConfig)); + NodeType = nodeType ?? throw new ArgumentNullException(nameof(nodeType)); + PassSpecifier = passSpecifier ?? throw new ArgumentNullException(nameof(passSpecifier)); + } + + public void Apply(LinkedList configs, IPatchProgress progress, IBasicLogger logger) + { + if (configs == null) throw new ArgumentNullException(nameof(configs)); + if (progress == null) throw new ArgumentNullException(nameof(progress)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + ConfigNode node = UrlConfig.config.DeepCopy(); + node.name = NodeType; + configs.AddLast(new ProtoUrlConfig(UrlConfig.parent, node)); + } + } +} diff --git a/ModuleManager/Patches/PatchCompiler.cs b/ModuleManager/Patches/PatchCompiler.cs index 11ce994b..67387372 100644 --- a/ModuleManager/Patches/PatchCompiler.cs +++ b/ModuleManager/Patches/PatchCompiler.cs @@ -13,18 +13,19 @@ public IPatch CompilePatch(ProtoPatch protoPatch) { if (protoPatch == null) throw new ArgumentNullException(nameof(protoPatch)); - INodeMatcher nodeMatcher = new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has); - switch (protoPatch.command) { + case Command.Insert: + return new InsertPatch(protoPatch.urlConfig, protoPatch.nodeType, protoPatch.passSpecifier); + case Command.Edit: - return new EditPatch(protoPatch.urlConfig, nodeMatcher, protoPatch.passSpecifier); + return new EditPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); case Command.Copy: - return new CopyPatch(protoPatch.urlConfig, nodeMatcher, protoPatch.passSpecifier); + return new CopyPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); case Command.Delete: - return new DeletePatch(protoPatch.urlConfig, nodeMatcher, protoPatch.passSpecifier); + return new DeletePatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); default: throw new ArgumentException("has an invalid command for a root node: " + protoPatch.command, nameof(protoPatch)); diff --git a/ModuleManager/Progress/IPatchProgress.cs b/ModuleManager/Progress/IPatchProgress.cs index 4edd07c2..d1b39cae 100644 --- a/ModuleManager/Progress/IPatchProgress.cs +++ b/ModuleManager/Progress/IPatchProgress.cs @@ -10,6 +10,7 @@ public interface IPatchProgress void Warning(UrlDir.UrlConfig url, string message); void Error(UrlDir.UrlConfig url, string message); + void Error(string message); void Exception(string message, Exception exception); void Exception(UrlDir.UrlConfig url, string message, Exception exception); void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url); @@ -18,9 +19,9 @@ public interface IPatchProgress void NeedsUnsatisfiedBefore(UrlDir.UrlConfig url); void NeedsUnsatisfiedFor(UrlDir.UrlConfig url); void NeedsUnsatisfiedAfter(UrlDir.UrlConfig url); - void ApplyingCopy(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); - void ApplyingDelete(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); - void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch); + void ApplyingCopy(IUrlConfigIdentifier original, UrlDir.UrlConfig patch); + void ApplyingDelete(IUrlConfigIdentifier original, UrlDir.UrlConfig patch); + void ApplyingUpdate(IUrlConfigIdentifier original, UrlDir.UrlConfig patch); void PatchAdded(); void PatchApplied(); } diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index 701fa4ef..d61c35eb 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -38,21 +38,21 @@ public void PatchAdded() Counter.totalPatches.Increment(); } - public void ApplyingUpdate(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) + public void ApplyingUpdate(IUrlConfigIdentifier original, UrlDir.UrlConfig patch) { - logger.Info($"Applying update {patch.SafeUrl()} to {original.SafeUrl()}"); + logger.Info($"Applying update {patch.SafeUrl()} to {original.FullUrl}"); Counter.patchedNodes.Increment(); } - public void ApplyingCopy(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) + public void ApplyingCopy(IUrlConfigIdentifier original, UrlDir.UrlConfig patch) { - logger.Info($"Applying copy {patch.SafeUrl()} to {original.SafeUrl()}"); + logger.Info($"Applying copy {patch.SafeUrl()} to {original.FullUrl}"); Counter.patchedNodes.Increment(); } - public void ApplyingDelete(UrlDir.UrlConfig original, UrlDir.UrlConfig patch) + public void ApplyingDelete(IUrlConfigIdentifier original, UrlDir.UrlConfig patch) { - logger.Info($"Applying delete {patch.SafeUrl()} to {original.SafeUrl()}"); + logger.Info($"Applying delete {patch.SafeUrl()} to {original.FullUrl}"); Counter.patchedNodes.Increment(); } @@ -109,6 +109,12 @@ public void Error(UrlDir.UrlConfig url, string message) RecordErrorFile(url); } + public void Error(string message) + { + Counter.errors.Increment(); + logger.Error(message); + } + public void Exception(string message, Exception exception) { Counter.exceptions.Increment(); diff --git a/ModuleManager/ProtoUrlConfig.cs b/ModuleManager/ProtoUrlConfig.cs new file mode 100644 index 00000000..74533948 --- /dev/null +++ b/ModuleManager/ProtoUrlConfig.cs @@ -0,0 +1,34 @@ +using System; + +namespace ModuleManager +{ + public interface IUrlConfigIdentifier + { + string FileUrl { get; } + string NodeType { get; } + string FullUrl { get; } + } + + public interface IProtoUrlConfig : IUrlConfigIdentifier + { + UrlDir.UrlFile UrlFile { get; } + ConfigNode Node { get; } + } + + public class ProtoUrlConfig : IProtoUrlConfig + { + public UrlDir.UrlFile UrlFile { get; } + public ConfigNode Node { get; } + public string FileUrl { get; } + public string NodeType => Node.name; + public string FullUrl { get; } + + public ProtoUrlConfig(UrlDir.UrlFile urlFile, ConfigNode node) + { + UrlFile = urlFile ?? throw new ArgumentNullException(nameof(urlFile)); + Node = node ?? throw new ArgumentNullException(nameof(node)); + FileUrl = UrlFile.url + '.' + urlFile.fileExtension; + FullUrl = FileUrl + '/' + Node.name; + } + } +} diff --git a/ModuleManagerTests/MMPatchLoaderTest.cs b/ModuleManagerTests/MMPatchLoaderTest.cs index efeb39a1..ac687909 100644 --- a/ModuleManagerTests/MMPatchLoaderTest.cs +++ b/ModuleManagerTests/MMPatchLoaderTest.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Xunit; using NSubstitute; using UnityEngine; @@ -15,7 +16,6 @@ public class MMPatchLoaderTest { private readonly IBasicLogger logger = Substitute.For(); private readonly IPatchProgress progress = Substitute.For(); - private readonly UrlDir root = UrlBuilder.CreateRoot(); [Fact] public void TestModifyNode__IndexAllWithAssign() @@ -29,9 +29,9 @@ public void TestModifyNode__IndexAllWithAssign() UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("@NODE") { { "@foo,*", "bar3" }, - }, root); + }); - PatchContext context = new PatchContext(c2u, root, logger, progress); + PatchContext context = new PatchContext(c2u, Enumerable.Empty(), logger, progress); ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context); @@ -56,9 +56,9 @@ public void TestModifyNode__MultiplyValue() UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("@NODE") { { "@foo *", "2" }, - }, root); + }); - PatchContext context = new PatchContext(c2u, root, logger, progress); + PatchContext context = new PatchContext(c2u, Enumerable.Empty(), logger, progress); ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context); diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 59998d7b..f07b5c07 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -91,6 +91,7 @@ + diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index fec4e148..81011b97 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using Xunit; using NSubstitute; using UnityEngine; -using TestUtils; using ModuleManager; using ModuleManager.Collections; using ModuleManager.Logging; @@ -35,25 +35,13 @@ public void TestConstructor__LoggerNull() Assert.Equal("logger", ex.ParamName); } - [Fact] - public void TestApplyPatches__UrlFilesNull() - { - PatchApplier applier = new PatchApplier(Substitute.For(), Substitute.For()); - ArgumentNullException ex = Assert.Throws(delegate - { - applier.ApplyPatches(null, new IPass[0]); - }); - - Assert.Equal("configFiles", ex.ParamName); - } - [Fact] public void TestApplyPatches__PatchesNull() { PatchApplier applier = new PatchApplier(Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { - applier.ApplyPatches(new UrlDir.UrlFile[0], null); + applier.ApplyPatches(null); }); Assert.Equal("patches", ex.ParamName); @@ -65,8 +53,6 @@ public void TestApplyPatches() IBasicLogger logger = Substitute.For(); IPatchProgress progress = Substitute.For(); PatchApplier patchApplier = new PatchApplier(progress, logger); - UrlDir.UrlFile file1 = UrlBuilder.CreateFile("abc/def.cfg"); - UrlDir.UrlFile file2 = UrlBuilder.CreateFile("ghi/jkl.cfg"); IPass pass1 = Substitute.For(); IPass pass2 = Substitute.For(); IPass pass3 = Substitute.For(); @@ -87,7 +73,7 @@ public void TestApplyPatches() IPass[] patchList = new IPass[] { pass1, pass2, pass3 }; - patchApplier.ApplyPatches(new[] { file1, file2 }, new[] { pass1, pass2, pass3 }); + LinkedList databaseConfigs = Assert.IsType>(patchApplier.ApplyPatches(new[] { pass1, pass2, pass3 })); progress.DidNotReceiveWithAnyArgs().Error(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null); @@ -100,34 +86,25 @@ public void TestApplyPatches() Received.InOrder(delegate { logger.Log(LogType.Log, ":PASS1 pass"); - patches[0].Apply(file1, progress, logger); - patches[0].Apply(file2, progress, logger); + patches[0].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); - patches[1].Apply(file1, progress, logger); - patches[1].Apply(file2, progress, logger); + patches[1].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); - patches[2].Apply(file1, progress, logger); - patches[2].Apply(file2, progress, logger); + patches[2].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); logger.Log(LogType.Log, ":PASS2 pass"); - patches[3].Apply(file1, progress, logger); - patches[3].Apply(file2, progress, logger); + patches[3].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); - patches[4].Apply(file1, progress, logger); - patches[4].Apply(file2, progress, logger); + patches[4].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); - patches[5].Apply(file1, progress, logger); - patches[5].Apply(file2, progress, logger); + patches[5].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); logger.Log(LogType.Log, ":PASS3 pass"); - patches[6].Apply(file1, progress, logger); - patches[6].Apply(file2, progress, logger); + patches[6].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); - patches[7].Apply(file1, progress, logger); - patches[7].Apply(file2, progress, logger); + patches[7].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); - patches[8].Apply(file1, progress, logger); - patches[8].Apply(file2, progress, logger); + patches[8].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); }); } diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index a7e2f0a0..4106486e 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -118,8 +118,6 @@ public void TestExtractPatch__ProtoPatchNull() AssertNoErrors(); - Assert.Empty(root.AllConfigs); - progress.DidNotReceive().PatchAdded(); progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } @@ -153,8 +151,6 @@ public void TestExtractPatch() AssertNoErrors(); - Assert.Empty(root.AllConfigs); - needsChecker.Received().CheckNeedsRecursive(urlConfig.config, urlConfig); needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsExpression(null); progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); @@ -190,8 +186,6 @@ public void TestExtractPatch__Needs() AssertNoErrors(); - Assert.Empty(root.AllConfigs); - needsChecker.Received().CheckNeedsRecursive(urlConfig.config, urlConfig); progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } @@ -222,8 +216,6 @@ public void TestExtractPatch__NeedsUnsatisfied() AssertNoErrors(); - Assert.Empty(root.AllConfigs); - passSpecifier.DidNotReceiveWithAnyArgs().CheckNeeds(null, null); needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsRecursive(null, null); patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null); @@ -258,65 +250,12 @@ public void TestExtractPatch__NeedsUnsatisfiedPassSpecifier() AssertNoErrors(); - Assert.Empty(root.AllConfigs); - needsChecker.DidNotReceiveWithAnyArgs().CheckNeedsRecursive(null, null); patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null); progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); } - [Fact] - public void TestExtractPatch__Insert() - { - UrlDir.UrlConfig urlConfig = CreateConfig("NODE_TYPE"); - - ITagList tagList = Substitute.For(); - tagListParser.Parse("NODE_TYPE", urlConfig).Returns(tagList); - - IPassSpecifier passSpecifier = Substitute.For(); - ProtoPatch protoPatch = new ProtoPatch( - urlConfig, - Command.Insert, - "NODE_TYPE", - null, - "needs", - null, - passSpecifier - ); - - protoPatchBuilder.Build(urlConfig, Command.Insert, tagList).Returns(protoPatch); - needsChecker.CheckNeedsExpression("needs").Returns(true); - passSpecifier.CheckNeeds(needsChecker, progress).Returns(true); - - ConfigNode needsCheckedNode = null; - needsChecker.CheckNeedsRecursive(Arg.Do(node => needsCheckedNode = node), urlConfig); - - Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - - AssertNoErrors(); - - Received.InOrder(delegate - { - tagListParser.Parse("NODE_TYPE", urlConfig); - protoPatchBuilder.Build(urlConfig, Command.Insert, tagList); - needsChecker.CheckNeedsExpression("needs"); - passSpecifier.CheckNeeds(needsChecker, progress); - needsChecker.CheckNeedsRecursive(needsCheckedNode, urlConfig); - }); - - patchCompiler.DidNotReceiveWithAnyArgs().CompilePatch(null); - - Assert.Single(root.AllConfigs); - UrlDir.UrlConfig newUrlConfig = root.AllConfigs.First(); - Assert.NotSame(urlConfig, newUrlConfig); - Assert.NotSame(urlConfig.config, newUrlConfig.config); - AssertConfigNodesEqual(urlConfig.config, newUrlConfig.config); - Assert.Same(needsCheckedNode, newUrlConfig.config); - - progress.DidNotReceiveWithAnyArgs().NeedsUnsatisfiedRoot(null); - } - [Fact] public void TestExtractPatch__Null() { @@ -337,8 +276,6 @@ public void TestExtractPatch__NotBracketBalanced() patchExtractor.ExtractPatch(config1); patchExtractor.ExtractPatch(config2); - Assert.Empty(root.AllConfigs); - progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); @@ -357,8 +294,6 @@ public void TestExtractPatch__InvalidCommand__Replace() UrlDir.UrlConfig urlConfig = CreateConfig("%NODE"); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - Assert.Empty(root.AllConfigs); - progress.Received().Error(urlConfig, "Error - replace command (%) is not valid on a root node: abc/def/%NODE"); } @@ -368,8 +303,6 @@ public void TestExtractPatch__InvalidCommand__Create() UrlDir.UrlConfig urlConfig = CreateConfig("&NODE"); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - Assert.Empty(root.AllConfigs); - progress.Received().Error(urlConfig, "Error - create command (&) is not valid on a root node: abc/def/&NODE"); } @@ -379,8 +312,6 @@ public void TestExtractPatch__InvalidCommand__Rename() UrlDir.UrlConfig urlConfig = CreateConfig("|NODE"); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - Assert.Empty(root.AllConfigs); - progress.Received().Error(urlConfig, "Error - rename command (|) is not valid on a root node: abc/def/|NODE"); } @@ -390,8 +321,6 @@ public void TestExtractPatch__InvalidCommand__Paste() UrlDir.UrlConfig urlConfig = CreateConfig("#NODE"); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - Assert.Empty(root.AllConfigs); - progress.Received().Error(urlConfig, "Error - paste command (#) is not valid on a root node: abc/def/#NODE"); } @@ -401,8 +330,6 @@ public void TestExtractPatch__InvalidCommand__Special() UrlDir.UrlConfig urlConfig = CreateConfig("*NODE"); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - Assert.Empty(root.AllConfigs); - progress.Received().Error(urlConfig, "Error - special command (*) is not valid on a root node: abc/def/*NODE"); } @@ -413,8 +340,6 @@ public void TestExtractPatch__TagListBadlyFormatted() tagListParser.When(t => t.Parse("badSomehow", urlConfig)).Throw(new FormatException("badly formatted")); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - Assert.Empty(root.AllConfigs); - progress.Received().Error(urlConfig, "Cannot parse node name as tag list: badly formatted\non: abc/def/badSomehow"); } @@ -426,8 +351,6 @@ public void TestExtractPatch__ProtoPatchFailed() Assert.Null(patchExtractor.ExtractPatch(urlConfig)); AssertNoErrors(); - - Assert.Empty(root.AllConfigs); } [Fact] @@ -438,8 +361,6 @@ public void TestExtractPatch__Exception() tagListParser.When(t => t.Parse("NODE", urlConfig)).Throw(ex); Assert.Null(patchExtractor.ExtractPatch(urlConfig)); - Assert.Empty(root.AllConfigs); - progress.Received().Exception(urlConfig, "Exception while attempting to create patch from config: abc/def/NODE", ex); } diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index d21c8c3f..8e66f0b3 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -111,32 +111,36 @@ public void Test__Lifecycle() Substitute.For(), Substitute.For(), Substitute.For(), + Substitute.For(), + Substitute.For(), }; UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); - patches[00].PassSpecifier.Returns(new FirstPassSpecifier()); - patches[01].PassSpecifier.Returns(new FirstPassSpecifier()); - patches[02].PassSpecifier.Returns(new LegacyPassSpecifier()); - patches[03].PassSpecifier.Returns(new LegacyPassSpecifier()); - patches[04].PassSpecifier.Returns(new BeforePassSpecifier("mod1", urlConfig)); - patches[05].PassSpecifier.Returns(new BeforePassSpecifier("MOD1", urlConfig)); - patches[06].PassSpecifier.Returns(new ForPassSpecifier("mod1", urlConfig)); - patches[07].PassSpecifier.Returns(new ForPassSpecifier("MOD1", urlConfig)); - patches[08].PassSpecifier.Returns(new AfterPassSpecifier("mod1", urlConfig)); - patches[09].PassSpecifier.Returns(new AfterPassSpecifier("MOD1", urlConfig)); - patches[10].PassSpecifier.Returns(new LastPassSpecifier("mod1")); - patches[11].PassSpecifier.Returns(new LastPassSpecifier("MOD1")); - patches[12].PassSpecifier.Returns(new BeforePassSpecifier("mod2", urlConfig)); - patches[13].PassSpecifier.Returns(new BeforePassSpecifier("MOD2", urlConfig)); - patches[14].PassSpecifier.Returns(new ForPassSpecifier("mod2", urlConfig)); - patches[15].PassSpecifier.Returns(new ForPassSpecifier("MOD2", urlConfig)); - patches[16].PassSpecifier.Returns(new AfterPassSpecifier("mod2", urlConfig)); - patches[17].PassSpecifier.Returns(new AfterPassSpecifier("MOD2", urlConfig)); - patches[18].PassSpecifier.Returns(new LastPassSpecifier("mod2")); - patches[19].PassSpecifier.Returns(new LastPassSpecifier("MOD2")); - patches[20].PassSpecifier.Returns(new FinalPassSpecifier()); - patches[21].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[00].PassSpecifier.Returns(new InsertPassSpecifier()); + patches[01].PassSpecifier.Returns(new InsertPassSpecifier()); + patches[02].PassSpecifier.Returns(new FirstPassSpecifier()); + patches[03].PassSpecifier.Returns(new FirstPassSpecifier()); + patches[04].PassSpecifier.Returns(new LegacyPassSpecifier()); + patches[05].PassSpecifier.Returns(new LegacyPassSpecifier()); + patches[06].PassSpecifier.Returns(new BeforePassSpecifier("mod1", urlConfig)); + patches[07].PassSpecifier.Returns(new BeforePassSpecifier("MOD1", urlConfig)); + patches[08].PassSpecifier.Returns(new ForPassSpecifier("mod1", urlConfig)); + patches[09].PassSpecifier.Returns(new ForPassSpecifier("MOD1", urlConfig)); + patches[10].PassSpecifier.Returns(new AfterPassSpecifier("mod1", urlConfig)); + patches[11].PassSpecifier.Returns(new AfterPassSpecifier("MOD1", urlConfig)); + patches[12].PassSpecifier.Returns(new LastPassSpecifier("mod1")); + patches[13].PassSpecifier.Returns(new LastPassSpecifier("MOD1")); + patches[14].PassSpecifier.Returns(new BeforePassSpecifier("mod2", urlConfig)); + patches[15].PassSpecifier.Returns(new BeforePassSpecifier("MOD2", urlConfig)); + patches[16].PassSpecifier.Returns(new ForPassSpecifier("mod2", urlConfig)); + patches[17].PassSpecifier.Returns(new ForPassSpecifier("MOD2", urlConfig)); + patches[18].PassSpecifier.Returns(new AfterPassSpecifier("mod2", urlConfig)); + patches[19].PassSpecifier.Returns(new AfterPassSpecifier("MOD2", urlConfig)); + patches[20].PassSpecifier.Returns(new LastPassSpecifier("mod2")); + patches[21].PassSpecifier.Returns(new LastPassSpecifier("MOD2")); + patches[22].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[23].PassSpecifier.Returns(new FinalPassSpecifier()); IPatchProgress progress = Substitute.For(); @@ -144,41 +148,44 @@ public void Test__Lifecycle() IPass[] passes = patchList.ToArray(); - Assert.Equal(11, passes.Length); + Assert.Equal(12, passes.Length); - Assert.Equal(":FIRST", passes[0].Name); + Assert.Equal(":INSERT (initial)", passes[0].Name); Assert.Equal(new[] { patches[0], patches[1] }, passes[0]); - Assert.Equal(":LEGACY (default)", passes[1].Name); + Assert.Equal(":FIRST", passes[1].Name); Assert.Equal(new[] { patches[2], patches[3] }, passes[1]); - Assert.Equal(":BEFORE[MOD1]", passes[2].Name); + Assert.Equal(":LEGACY (default)", passes[2].Name); Assert.Equal(new[] { patches[4], patches[5] }, passes[2]); - Assert.Equal(":FOR[MOD1]", passes[3].Name); + Assert.Equal(":BEFORE[MOD1]", passes[3].Name); Assert.Equal(new[] { patches[6], patches[7] }, passes[3]); - Assert.Equal(":AFTER[MOD1]", passes[4].Name); + Assert.Equal(":FOR[MOD1]", passes[4].Name); Assert.Equal(new[] { patches[8], patches[9] }, passes[4]); - Assert.Equal(":BEFORE[MOD2]", passes[5].Name); - Assert.Equal(new[] { patches[12], patches[13] }, passes[5]); + Assert.Equal(":AFTER[MOD1]", passes[5].Name); + Assert.Equal(new[] { patches[10], patches[11] }, passes[5]); - Assert.Equal(":FOR[MOD2]", passes[6].Name); + Assert.Equal(":BEFORE[MOD2]", passes[6].Name); Assert.Equal(new[] { patches[14], patches[15] }, passes[6]); - Assert.Equal(":AFTER[MOD2]", passes[7].Name); + Assert.Equal(":FOR[MOD2]", passes[7].Name); Assert.Equal(new[] { patches[16], patches[17] }, passes[7]); - Assert.Equal(":LAST[MOD1]", passes[8].Name); - Assert.Equal(new[] { patches[10], patches[11] }, passes[8]); + Assert.Equal(":AFTER[MOD2]", passes[8].Name); + Assert.Equal(new[] { patches[18], patches[19] }, passes[8]); - Assert.Equal(":LAST[MOD2]", passes[9].Name); - Assert.Equal(new[] { patches[18], patches[19] }, passes[9]); + Assert.Equal(":LAST[MOD1]", passes[9].Name); + Assert.Equal(new[] { patches[12], patches[13] }, passes[9]); - Assert.Equal(":FINAL", passes[10].Name); + Assert.Equal(":LAST[MOD2]", passes[10].Name); Assert.Equal(new[] { patches[20], patches[21] }, passes[10]); + Assert.Equal(":FINAL", passes[11].Name); + Assert.Equal(new[] { patches[22], patches[23] }, passes[11]); + progress.Received(patches.Length).PatchAdded(); } } diff --git a/ModuleManagerTests/Patches/CopyPatchTest.cs b/ModuleManagerTests/Patches/CopyPatchTest.cs index 635e5e22..c0e8e14a 100644 --- a/ModuleManagerTests/Patches/CopyPatchTest.cs +++ b/ModuleManagerTests/Patches/CopyPatchTest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Xunit; using NSubstitute; using TestUtils; @@ -77,72 +79,97 @@ public void TestApply() { UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); - UrlDir.UrlConfig urlConfig1 = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config1 = new TestConfigNode("NODE") { { "foo", "bar" }, - }, file); + }; - UrlDir.UrlConfig urlConfig2 = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config2 = new TestConfigNode("NODE") { { "foo", "bar" }, - }, file); + }; - UrlDir.UrlConfig urlConfig3 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); - UrlDir.UrlConfig urlConfig4 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + ConfigNode config3 = new ConfigNode("NODE"); + ConfigNode config4 = new ConfigNode("NODE"); INodeMatcher nodeMatcher = Substitute.For(); - nodeMatcher.IsMatch(urlConfig1.config).Returns(false); - nodeMatcher.IsMatch(urlConfig2.config).Returns(true); - nodeMatcher.IsMatch(urlConfig3.config).Returns(false); - nodeMatcher.IsMatch(urlConfig4.config).Returns(true); + nodeMatcher.IsMatch(config1).Returns(false); + nodeMatcher.IsMatch(config2).Returns(true); + nodeMatcher.IsMatch(config3).Returns(false); + nodeMatcher.IsMatch(config4).Returns(true); CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") { { "@foo", "baz" }, { "pqr", "stw" }, }), nodeMatcher, Substitute.For()); - + + IProtoUrlConfig protoUrlConfig1 = Substitute.For(); + IProtoUrlConfig protoUrlConfig2 = Substitute.For(); + IProtoUrlConfig protoUrlConfig3 = Substitute.For(); + IProtoUrlConfig protoUrlConfig4 = Substitute.For(); + + protoUrlConfig1.Node.Returns(config1); + protoUrlConfig2.Node.Returns(config2); + protoUrlConfig3.Node.Returns(config3); + protoUrlConfig4.Node.Returns(config4); + + protoUrlConfig1.UrlFile.Returns(file); + protoUrlConfig2.UrlFile.Returns(file); + protoUrlConfig3.UrlFile.Returns(file); + protoUrlConfig4.UrlFile.Returns(file); + + LinkedList configs = new LinkedList(); + configs.AddLast(protoUrlConfig1); + configs.AddLast(protoUrlConfig2); + configs.AddLast(protoUrlConfig3); + configs.AddLast(protoUrlConfig4); + IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); - patch.Apply(file, progress, logger); + patch.Apply(configs, progress, logger); + + IProtoUrlConfig[] newConfigs = configs.ToArray(); - Assert.Equal(6, file.configs.Count); + Assert.Equal(6, newConfigs.Length); - Assert.Same(urlConfig1, file.configs[0]); + Assert.Same(protoUrlConfig1, newConfigs[0]); AssertNodesEqual(new TestConfigNode("NODE") { { "foo", "bar" }, - }, file.configs[0].config); + }, newConfigs[0].Node); - Assert.Same(urlConfig2, file.configs[1]); + Assert.Same(protoUrlConfig2, newConfigs[1]); AssertNodesEqual(new TestConfigNode("NODE") { { "foo", "bar" }, - }, file.configs[1].config); + }, newConfigs[1].Node); - Assert.Same(urlConfig3, file.configs[2]); - AssertNodesEqual(new ConfigNode("NODE"), file.configs[2].config); - - Assert.Same(urlConfig4, file.configs[3]); - AssertNodesEqual(new ConfigNode("NODE"), file.configs[3].config); - AssertNodesEqual(new TestConfigNode("NODE") { { "foo", "baz" }, { "pqr", "stw" }, - }, file.configs[4].config); + }, newConfigs[2].Node); + Assert.Same(file, newConfigs[2].UrlFile); + + Assert.Same(protoUrlConfig3, newConfigs[3]); + AssertNodesEqual(new ConfigNode("NODE"), newConfigs[3].Node); + + Assert.Same(protoUrlConfig4, newConfigs[4]); + AssertNodesEqual(new ConfigNode("NODE"), newConfigs[4].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "pqr", "stw" }, - }, file.configs[5].config); + }, newConfigs[5].Node); + Assert.Same(file, newConfigs[5].UrlFile); Received.InOrder(delegate { - progress.ApplyingCopy(urlConfig2, patch.UrlConfig); - progress.ApplyingCopy(urlConfig4, patch.UrlConfig); + progress.ApplyingCopy(protoUrlConfig2, patch.UrlConfig); + progress.ApplyingCopy(protoUrlConfig4, patch.UrlConfig); }); progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); @@ -158,15 +185,15 @@ public void TestApply__NameChanged() { UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config = new TestConfigNode("NODE") { { "name", "000" }, { "foo", "bar" }, - }, file); + }; INodeMatcher nodeMatcher = Substitute.For(); - nodeMatcher.IsMatch(urlConfig.config).Returns(true); + nodeMatcher.IsMatch(config).Returns(true); CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") { @@ -175,28 +202,38 @@ public void TestApply__NameChanged() { "pqr", "stw" }, }), nodeMatcher, Substitute.For()); + IProtoUrlConfig protoConfig = Substitute.For(); + protoConfig.Node.Returns(config); + protoConfig.UrlFile.Returns(file); + + LinkedList configs = new LinkedList(); + configs.AddLast(protoConfig); + IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); - patch.Apply(file, progress, logger); + patch.Apply(configs, progress, logger); + + IProtoUrlConfig[] newConfigs = configs.ToArray(); - Assert.Equal(2, file.configs.Count); + Assert.Equal(2, newConfigs.Length); - Assert.Same(urlConfig, file.configs[0]); + Assert.Same(protoConfig, newConfigs[0]); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "000" }, { "foo", "bar" }, - }, file.configs[0].config); + }, newConfigs[0].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "001" }, { "foo", "baz" }, { "pqr", "stw" }, - }, file.configs[1].config); + }, newConfigs[1].Node); + Assert.Same(file, newConfigs[1].UrlFile); - progress.Received().ApplyingCopy(urlConfig, patch.UrlConfig); + progress.Received().ApplyingCopy(protoConfig, patch.UrlConfig); progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingDelete(null, null); @@ -209,17 +246,15 @@ public void TestApply__NameChanged() [Fact] public void TestApply__NameNotChanged() { - UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); - - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config = new TestConfigNode("NODE") { { "name", "000" }, { "foo", "bar" }, - }, file); + }; INodeMatcher nodeMatcher = Substitute.For(); - nodeMatcher.IsMatch(urlConfig.config).Returns(true); + nodeMatcher.IsMatch(config).Returns(true); CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("+NODE") { @@ -227,21 +262,28 @@ public void TestApply__NameNotChanged() { "pqr", "stw" }, }), nodeMatcher, Substitute.For()); + IProtoUrlConfig protoConfig = Substitute.For(); + protoConfig.Node.Returns(config); + protoConfig.FullUrl.Returns("abc/def.cfg/NODE"); + + LinkedList configs = new LinkedList(); + configs.AddLast(protoConfig); + IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); - patch.Apply(file, progress, logger); + patch.Apply(configs, progress, logger); - Assert.Single(file.configs); + Assert.Single(configs); - Assert.Same(urlConfig, file.configs[0]); + Assert.Same(protoConfig, configs.First.Value); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "000" }, { "foo", "bar" }, - }, file.configs[0].config); + }, configs.First.Value.Node); - progress.Received().Error(patch.UrlConfig, "Error - when applying copy ghi/jkl/+NODE to abc/def/NODE - the copy needs to have a different name than the parent (use @name = xxx)"); + progress.Received().Error(patch.UrlConfig, "Error - when applying copy ghi/jkl/+NODE to abc/def.cfg/NODE - the copy needs to have a different name than the parent (use @name = xxx)"); progress.DidNotReceiveWithAnyArgs().ApplyingUpdate(null, null); progress.DidNotReceiveWithAnyArgs().ApplyingCopy(null, null); @@ -252,7 +294,7 @@ public void TestApply__NameNotChanged() } [Fact] - public void TestApply__FileNull() + public void TestApply__DatabaseConfigsNullNull() { CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate @@ -260,7 +302,7 @@ public void TestApply__FileNull() patch.Apply(null, Substitute.For(), Substitute.For()); }); - Assert.Equal("file", ex.ParamName); + Assert.Equal("databaseConfigs", ex.ParamName); } [Fact] @@ -269,7 +311,7 @@ public void TestApply__ProgressNull() CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { - patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); + patch.Apply(new LinkedList(), null, Substitute.For()); }); Assert.Equal("progress", ex.ParamName); @@ -281,7 +323,7 @@ public void TestApply__LoggerNull() CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { - patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); + patch.Apply(new LinkedList(), Substitute.For(), null); }); Assert.Equal("logger", ex.ParamName); diff --git a/ModuleManagerTests/Patches/DeletePatchTest.cs b/ModuleManagerTests/Patches/DeletePatchTest.cs index 743b7bb2..1cfa950d 100644 --- a/ModuleManagerTests/Patches/DeletePatchTest.cs +++ b/ModuleManagerTests/Patches/DeletePatchTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Xunit; using NSubstitute; using TestUtils; @@ -75,28 +76,42 @@ public void TestPassSpecifier() [Fact] public void TestApply() { - UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); - - UrlDir.UrlConfig urlConfig1 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); - UrlDir.UrlConfig urlConfig2 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); - UrlDir.UrlConfig urlConfig3 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); - UrlDir.UrlConfig urlConfig4 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + ConfigNode config1 = new ConfigNode("NODE"); + ConfigNode config2 = new ConfigNode("NODE"); + ConfigNode config3 = new ConfigNode("NODE"); + ConfigNode config4 = new ConfigNode("NODE"); INodeMatcher nodeMatcher = Substitute.For(); - nodeMatcher.IsMatch(urlConfig1.config).Returns(false); - nodeMatcher.IsMatch(urlConfig2.config).Returns(true); - nodeMatcher.IsMatch(urlConfig3.config).Returns(false); - nodeMatcher.IsMatch(urlConfig4.config).Returns(true); + nodeMatcher.IsMatch(config1).Returns(false); + nodeMatcher.IsMatch(config2).Returns(true); + nodeMatcher.IsMatch(config3).Returns(false); + nodeMatcher.IsMatch(config4).Returns(true); DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!NODE")), nodeMatcher, Substitute.For()); + IProtoUrlConfig urlConfig1 = Substitute.For(); + IProtoUrlConfig urlConfig2 = Substitute.For(); + IProtoUrlConfig urlConfig3 = Substitute.For(); + IProtoUrlConfig urlConfig4 = Substitute.For(); + + urlConfig1.Node.Returns(config1); + urlConfig2.Node.Returns(config2); + urlConfig3.Node.Returns(config3); + urlConfig4.Node.Returns(config4); + + LinkedList configs = new LinkedList(); + configs.AddLast(urlConfig1); + configs.AddLast(urlConfig2); + configs.AddLast(urlConfig3); + configs.AddLast(urlConfig4); + IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); - patch.Apply(file, progress, logger); + patch.Apply(configs, progress, logger); - Assert.Equal(new[] { urlConfig1, urlConfig3 }, file.configs); + Assert.Equal(new[] { urlConfig1, urlConfig3 }, configs); Received.InOrder(delegate { @@ -113,7 +128,7 @@ public void TestApply() } [Fact] - public void TestApply__FileNull() + public void TestApply__DatabaseConfigsNull() { DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate @@ -121,7 +136,7 @@ public void TestApply__FileNull() patch.Apply(null, Substitute.For(), Substitute.For()); }); - Assert.Equal("file", ex.ParamName); + Assert.Equal("databaseConfigs", ex.ParamName); } [Fact] @@ -130,7 +145,7 @@ public void TestApply__ProgressNull() DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { - patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); + patch.Apply(new LinkedList(), null, Substitute.For()); }); Assert.Equal("progress", ex.ParamName); @@ -142,7 +157,7 @@ public void TestApply__LoggerNull() DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { - patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); + patch.Apply(new LinkedList(), Substitute.For(), null); }); Assert.Equal("logger", ex.ParamName); diff --git a/ModuleManagerTests/Patches/EditPatchTest.cs b/ModuleManagerTests/Patches/EditPatchTest.cs index e72963f1..7ce5f6a9 100644 --- a/ModuleManagerTests/Patches/EditPatchTest.cs +++ b/ModuleManagerTests/Patches/EditPatchTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Xunit; using NSubstitute; using UnityEngine; @@ -79,60 +80,83 @@ public void TestApply() { UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); - UrlDir.UrlConfig urlConfig1 = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config1 = new TestConfigNode("NODE") { { "foo", "bar" }, - }, file); + }; - UrlDir.UrlConfig urlConfig2 = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config2 = new TestConfigNode("NODE") { { "foo", "bar" }, - }, file); + }; - UrlDir.UrlConfig urlConfig3 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); - UrlDir.UrlConfig urlConfig4 = UrlBuilder.CreateConfig(new ConfigNode("NODE"), file); + ConfigNode config3 = new ConfigNode("NODE"); + ConfigNode config4 = new ConfigNode("NODE"); INodeMatcher nodeMatcher = Substitute.For(); - nodeMatcher.IsMatch(urlConfig1.config).Returns(false); - nodeMatcher.IsMatch(urlConfig2.config).Returns(true); - nodeMatcher.IsMatch(urlConfig3.config).Returns(false); - nodeMatcher.IsMatch(urlConfig4.config).Returns(true); + nodeMatcher.IsMatch(config1).Returns(false); + nodeMatcher.IsMatch(config2).Returns(true); + nodeMatcher.IsMatch(config3).Returns(false); + nodeMatcher.IsMatch(config4).Returns(true); EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("ghi/jkl", new TestConfigNode("@NODE") { { "@foo", "baz" }, { "pqr", "stw" }, }), nodeMatcher, Substitute.For()); - + + IProtoUrlConfig urlConfig1 = Substitute.For(); + IProtoUrlConfig urlConfig2 = Substitute.For(); + IProtoUrlConfig urlConfig3 = Substitute.For(); + IProtoUrlConfig urlConfig4 = Substitute.For(); + + urlConfig1.Node.Returns(config1); + urlConfig2.Node.Returns(config2); + urlConfig3.Node.Returns(config3); + urlConfig4.Node.Returns(config4); + + urlConfig1.UrlFile.Returns(file); + urlConfig2.UrlFile.Returns(file); + urlConfig3.UrlFile.Returns(file); + urlConfig4.UrlFile.Returns(file); + + LinkedList configs = new LinkedList(); + configs.AddLast(urlConfig1); + configs.AddLast(urlConfig2); + configs.AddLast(urlConfig3); + configs.AddLast(urlConfig4); + IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); - patch.Apply(file, progress, logger); + patch.Apply(configs, progress, logger); + + IProtoUrlConfig[] newConfigs = configs.ToArray(); - Assert.Equal(4, file.configs.Count); + Assert.Equal(4, newConfigs.Length); - Assert.Same(urlConfig1, file.configs[0]); + Assert.Same(urlConfig1, newConfigs[0]); AssertNodesEqual(new TestConfigNode("NODE") { { "foo", "bar" }, - }, file.configs[0].config); + }, newConfigs[0].Node); - Assert.NotSame(urlConfig2, file.configs[1]); AssertNodesEqual(new TestConfigNode("NODE") { { "foo", "baz" }, { "pqr", "stw" }, - }, file.configs[1].config); + }, newConfigs[1].Node); + Assert.Same(file, newConfigs[1].UrlFile); - Assert.Same(urlConfig3, file.configs[2]); - AssertNodesEqual(new ConfigNode("NODE"), file.configs[2].config); + Assert.Same(urlConfig3, newConfigs[2]); + AssertNodesEqual(new ConfigNode("NODE"), newConfigs[2].Node); - Assert.NotSame(urlConfig4, file.configs[3]); AssertNodesEqual(new TestConfigNode("NODE") { { "pqr", "stw" }, - }, file.configs[3].config); + }, newConfigs[3].Node); + Assert.Same(file, newConfigs[3].UrlFile); Received.InOrder(delegate { @@ -153,11 +177,11 @@ public void TestApply__Loop() { UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config = new TestConfigNode("NODE") { { "name", "000" }, { "aaa", "1" }, - }, file); + }; INodeMatcher nodeMatcher = Substitute.For(); @@ -170,16 +194,23 @@ public void TestApply__Loop() new ConfigNode("MM_PATCH_LOOP"), }), nodeMatcher, Substitute.For()); + IProtoUrlConfig urlConfig = Substitute.For(); + urlConfig.Node.Returns(config); + urlConfig.UrlFile.Returns(file); + urlConfig.FullUrl.Returns("abc/def.cfg/NODE"); + + LinkedList configs = new LinkedList(); + configs.AddLast(urlConfig); + IPatchProgress progress = Substitute.For(); IBasicLogger logger = Substitute.For(); - List modifiedUrlConfigs = new List(); - progress.ApplyingUpdate(Arg.Do(url => modifiedUrlConfigs.Add(url)), patch.UrlConfig); + List modifiedUrlConfigs = new List(); + progress.ApplyingUpdate(Arg.Do(url => modifiedUrlConfigs.Add(url)), patch.UrlConfig); - patch.Apply(file, progress, logger); + patch.Apply(configs, progress, logger); - Assert.Single(file.configs); - Assert.NotSame(urlConfig, file.configs[0]); + Assert.Single(configs); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "000" }, @@ -188,7 +219,8 @@ public void TestApply__Loop() { "bbb", "002" }, { "bbb", "002" }, { "bbb", "002" }, - }, file.configs[0].config); + }, configs.First.Value.Node); + Assert.Same(file, configs.First.Value.UrlFile); Assert.Same(urlConfig, modifiedUrlConfigs[0]); Assert.NotSame(urlConfig, modifiedUrlConfigs[1]); @@ -197,7 +229,7 @@ public void TestApply__Loop() Received.InOrder(delegate { - logger.Log(LogType.Log, "Looping on ghi/jkl/@NODE to abc/def/NODE"); + logger.Log(LogType.Log, "Looping on ghi/jkl/@NODE to abc/def.cfg/NODE"); progress.ApplyingUpdate(urlConfig, patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[1], patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[2], patch.UrlConfig); @@ -213,7 +245,7 @@ public void TestApply__Loop() } [Fact] - public void TestApply__FileNull() + public void TestApply__DatabaseConfigsNull() { EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate @@ -221,7 +253,7 @@ public void TestApply__FileNull() patch.Apply(null, Substitute.For(), Substitute.For()); }); - Assert.Equal("file", ex.ParamName); + Assert.Equal("databaseConfigs", ex.ParamName); } [Fact] @@ -230,7 +262,7 @@ public void TestApply__ProgressNull() EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { - patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), null, Substitute.For()); + patch.Apply(new LinkedList(), null, Substitute.For()); }); Assert.Equal("progress", ex.ParamName); @@ -242,7 +274,7 @@ public void TestApply__LoggerNull() EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); ArgumentNullException ex = Assert.Throws(delegate { - patch.Apply(UrlBuilder.CreateFile("abc/def.cfg"), Substitute.For(), null); + patch.Apply(new LinkedList(), Substitute.For(), null); }); Assert.Equal("logger", ex.ParamName); diff --git a/ModuleManagerTests/Patches/InsertPatchTest.cs b/ModuleManagerTests/Patches/InsertPatchTest.cs new file mode 100644 index 00000000..b9944a9d --- /dev/null +++ b/ModuleManagerTests/Patches/InsertPatchTest.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; +using NSubstitute; +using TestUtils; +using ModuleManager; +using ModuleManager.Logging; +using ModuleManager.Patches; +using ModuleManager.Patches.PassSpecifiers; +using ModuleManager.Progress; + + +namespace ModuleManagerTests.Patches +{ + public class InsertPatchTest + { + [Fact] + public void TestConstructor__UrlConfigNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new InsertPatch(null, "A_NODE", Substitute.For()); + }); + + Assert.Equal("urlConfig", ex.ParamName); + } + + [Fact] + public void TestConstructor__NodeTypeNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new InsertPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), null, Substitute.For()); + }); + + Assert.Equal("nodeType", ex.ParamName); + } + + [Fact] + public void TestConstructor__PassSpecifierNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new InsertPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), "A_NODE", null); + }); + + Assert.Equal("passSpecifier", ex.ParamName); + } + + [Fact] + public void TestUrlConfig() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode()); + InsertPatch patch = new InsertPatch(urlConfig, "A_NODE", Substitute.For()); + + Assert.Same(urlConfig, patch.UrlConfig); + } + + [Fact] + public void TestNodeType() + { + InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), "A_NODE", Substitute.For()); + + Assert.Equal("A_NODE", patch.NodeType); + } + + [Fact] + public void TestPassSpecifier() + { + IPassSpecifier passSpecifier = Substitute.For(); + InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), "A_NODE", passSpecifier); + + Assert.Same(passSpecifier, patch.PassSpecifier); + } + + [Fact] + public void TestApply() + { + UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("A_NODE:NEEDS[someMod]:FOR[somePass]") + { + { "key1", "value1" }, + { "key2", "value2" }, + new TestConfigNode("NODE_1") + { + { "key3", "value3" }, + }, + new TestConfigNode("NODE_2") + { + { "key4", "value4" }, + }, + }); + + InsertPatch patch = new InsertPatch(urlConfig, "A_NODE", Substitute.For()); + + LinkedList databaseConfigs = new LinkedList(); + + IProtoUrlConfig config1 = Substitute.For(); + IProtoUrlConfig config2 = Substitute.For(); + + databaseConfigs.AddLast(config1); + databaseConfigs.AddLast(config2); + + patch.Apply(databaseConfigs, Substitute.For(), Substitute.For()); + + IProtoUrlConfig[] databaseConfigsArray = databaseConfigs.ToArray(); + Assert.Equal(3, databaseConfigsArray.Length); + Assert.Same(config1, databaseConfigsArray[0]); + Assert.Same(config2, databaseConfigsArray[1]); + + Assert.Same(urlConfig.parent, databaseConfigsArray[2].UrlFile); + Assert.Equal("abc/def.cfg", databaseConfigsArray[2].FileUrl); + Assert.Equal("A_NODE", databaseConfigsArray[2].NodeType); + Assert.Equal("abc/def.cfg/A_NODE", databaseConfigsArray[2].FullUrl); + + Assert.NotSame(urlConfig.config, databaseConfigsArray[2].Node); + Assert.Equal("A_NODE", databaseConfigsArray[2].Node.name); + Assert.Equal("A_NODE:NEEDS[someMod]:FOR[somePass]", urlConfig.config.name); // make sure this hasn't been changed + Assert.Equal(2, databaseConfigsArray[2].Node.values.Count); + Assert.Equal("key1", databaseConfigsArray[2].Node.values[0].name); + Assert.Equal("value1", databaseConfigsArray[2].Node.values[0].value); + Assert.Equal("key2", databaseConfigsArray[2].Node.values[1].name); + Assert.Equal("value2", databaseConfigsArray[2].Node.values[1].value); + Assert.Equal(2, databaseConfigsArray[2].Node.nodes.Count); + Assert.Equal("NODE_1", databaseConfigsArray[2].Node.nodes[0].name); + Assert.Equal(1, databaseConfigsArray[2].Node.nodes[0].values.Count); + Assert.Equal("key3", databaseConfigsArray[2].Node.nodes[0].values[0].name); + Assert.Equal("value3", databaseConfigsArray[2].Node.nodes[0].values[0].value); + Assert.Equal(0, databaseConfigsArray[2].Node.nodes[0].nodes.Count); + Assert.Equal("NODE_2", databaseConfigsArray[2].Node.nodes[1].name); + Assert.Equal(1, databaseConfigsArray[2].Node.nodes[1].values.Count); + Assert.Equal("key4", databaseConfigsArray[2].Node.nodes[1].values[0].name); + Assert.Equal("value4", databaseConfigsArray[2].Node.nodes[1].values[0].value); + Assert.Equal(0, databaseConfigsArray[2].Node.nodes[1].nodes.Count); + } + + [Fact] + public void TestApply__DatabaseConfigsNull() + { + InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), "A_NODE", Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(null, Substitute.For(), Substitute.For()); + }); + + Assert.Equal("configs", ex.ParamName); + } + + [Fact] + public void TestApply__ProgressNull() + { + InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), "A_NODE", Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(new LinkedList(), null, Substitute.For()); + }); + + Assert.Equal("progress", ex.ParamName); + } + + [Fact] + public void TestApply__LoggerNull() + { + InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), "A_NODE", Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + patch.Apply(new LinkedList(), Substitute.For(), null); + }); + + Assert.Equal("logger", ex.ParamName); + } + } +} diff --git a/ModuleManagerTests/Patches/PatchCompilerTest.cs b/ModuleManagerTests/Patches/PatchCompilerTest.cs index 5e660876..5ad3c794 100644 --- a/ModuleManagerTests/Patches/PatchCompilerTest.cs +++ b/ModuleManagerTests/Patches/PatchCompilerTest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Xunit; using NSubstitute; using TestUtils; @@ -17,6 +19,41 @@ public class PatchCompilerTest private readonly UrlDir.UrlFile file = UrlBuilder.CreateFile("abc/def.cfg"); private readonly PatchCompiler patchCompiler = new PatchCompiler(); + [Fact] + public void TestCompilePatch__Insert() + { + ProtoPatch protoPatch = new ProtoPatch( + UrlBuilder.CreateConfig(new TestConfigNode("NODEE") + { + { "name", "foo" }, + { "bar", "bleh" }, + }, file), + Command.Insert, + "NODE", + "foo", + null, + "#bar", + Substitute.For() + ); + + InsertPatch patch = Assert.IsType(patchCompiler.CompilePatch(protoPatch)); + + Assert.Same(protoPatch.urlConfig, patch.UrlConfig); + + LinkedList configs = new LinkedList(); + + patch.Apply(configs, progress, logger); + + Assert.Equal(1, configs.Count); + Assert.NotSame(protoPatch.urlConfig.config, configs.First.Value.Node); + AssertNodesEqual(new TestConfigNode("NODE") + { + { "name", "foo" }, + { "bar", "bleh" }, + }, configs.First.Value.Node); + Assert.Same(file, configs.First.Value.UrlFile); + } + [Fact] public void TestCompilePatch__Edit() { @@ -38,25 +75,33 @@ public void TestCompilePatch__Edit() Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config = new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "baz" }, - }, file); + }; - patch.Apply(file, progress, logger); + IProtoUrlConfig urlConfig = Substitute.For(); + urlConfig.Node.Returns(config); + urlConfig.UrlFile.Returns(file); + + LinkedList configs = new LinkedList(); + configs.AddLast(urlConfig); + + patch.Apply(configs, progress, logger); AssertNoErrors(); progress.Received().ApplyingUpdate(urlConfig, protoPatch.urlConfig); - Assert.Single(file.configs); - Assert.NotSame(urlConfig, file.configs[0]); + Assert.Single(configs); + Assert.NotSame(config, configs.First.Value.Node); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "bleh" }, - }, file.configs[0].config); + }, configs.First.Value.Node); + Assert.Same(file, configs.First.Value.UrlFile); } [Fact] @@ -81,30 +126,40 @@ public void TestCompilePatch__Copy() Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + ConfigNode config = new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "baz" }, - }, file); + }; + + IProtoUrlConfig urlConfig = Substitute.For(); + urlConfig.Node.Returns(config); + urlConfig.UrlFile.Returns(file); + + LinkedList configs = new LinkedList(); + configs.AddLast(urlConfig); - patch.Apply(file, progress, logger); + patch.Apply(configs, progress, logger); AssertNoErrors(); progress.Received().ApplyingCopy(urlConfig, protoPatch.urlConfig); - Assert.Equal(2, file.configs.Count); - Assert.Same(urlConfig, file.configs[0]); + IProtoUrlConfig[] newConfigs = configs.ToArray(); + + Assert.Equal(2, newConfigs.Length); + Assert.Same(config, newConfigs[0].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "baz" }, - }, file.configs[0].config); + }, newConfigs[0].Node); AssertNodesEqual(new TestConfigNode("NODE") { { "name", "boo" }, { "bar", "bleh" }, - }, file.configs[1].config); + }, newConfigs[1].Node); + Assert.Same(file, newConfigs[1].UrlFile); } [Fact] @@ -125,19 +180,23 @@ public void TestCompilePatch__Delete() Assert.Same(protoPatch.urlConfig, patch.UrlConfig); AssertNodeMatcher(patch.NodeMatcher); - UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig(new TestConfigNode("NODE") + IProtoUrlConfig urlConfig = Substitute.For(); + urlConfig.Node.Returns(new TestConfigNode("NODE") { { "name", "foo" }, { "bar", "baz" }, - }, file); + }); + + LinkedList configs = new LinkedList(); + configs.AddLast(urlConfig); - patch.Apply(file, progress, logger); + patch.Apply(configs, progress, logger); AssertNoErrors(); progress.Received().ApplyingDelete(urlConfig, protoPatch.urlConfig); - Assert.Empty(file.configs); + Assert.Empty(configs); } [Fact] diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index caaced90..c7f448e1 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -3,9 +3,9 @@ using NSubstitute; using UnityEngine; using TestUtils; +using ModuleManager; using ModuleManager.Logging; using ModuleManager.Progress; -using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManagerTests { @@ -30,13 +30,14 @@ public void Test__Constructor__Nested() Assert.Equal(0, progress.Counter.patchedNodes); - UrlDir.UrlConfig original = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + IProtoUrlConfig original = Substitute.For(); + original.FullUrl.Returns("abc/def.cfg/SOME_NODE"); UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("@SOME_NODE")); progress2.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); - logger2.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); + logger2.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -52,7 +53,8 @@ public void TestPatchAdded() [Fact] public void TestApplyingUpdate() { - UrlDir.UrlConfig original = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + IProtoUrlConfig original = Substitute.For(); + original.FullUrl.Returns("abc/def.cfg/SOME_NODE"); UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("@SOME_NODE")); UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("@SOME_NODE")); @@ -60,17 +62,18 @@ public void TestApplyingUpdate() progress.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingUpdate(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying update pqr/stu/@SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying update pqr/stu/@SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] public void TesApplyingCopy() { - UrlDir.UrlConfig original = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + IProtoUrlConfig original = Substitute.For(); + original.FullUrl.Returns("abc/def.cfg/SOME_NODE"); UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("+SOME_NODE")); UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("+SOME_NODE")); @@ -78,17 +81,18 @@ public void TesApplyingCopy() progress.ApplyingCopy(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying copy ghi/jkl/+SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying copy ghi/jkl/+SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingCopy(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying copy pqr/stu/+SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying copy pqr/stu/+SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] public void TesApplyingDelete() { - UrlDir.UrlConfig original = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); + IProtoUrlConfig original = Substitute.For(); + original.FullUrl.Returns("abc/def.cfg/SOME_NODE"); UrlDir.UrlConfig patch1 = UrlBuilder.CreateConfig("ghi/jkl", new ConfigNode("!SOME_NODE")); UrlDir.UrlConfig patch2 = UrlBuilder.CreateConfig("pqr/stu", new ConfigNode("!SOME_NODE")); @@ -96,11 +100,11 @@ public void TesApplyingDelete() progress.ApplyingDelete(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying delete ghi/jkl/!SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying delete ghi/jkl/!SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingDelete(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying delete pqr/stu/!SOME_NODE to abc/def/SOME_NODE"); + logger.Received().Log(LogType.Log, "Applying delete pqr/stu/!SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -236,6 +240,18 @@ public void TestWarning() [Fact] public void TestError() + { + Assert.Equal(0, progress.Counter.errors); + + progress.Error("An error message no one is going to read"); + Assert.Equal(1, progress.Counter.errors); + + progress.Error("Maybe someone will read this one"); + Assert.Equal(2, progress.Counter.errors); + } + + [Fact] + public void TestError__Config() { UrlDir.UrlConfig config1 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_NODE")); UrlDir.UrlConfig config2 = UrlBuilder.CreateConfig("abc/def", new ConfigNode("SOME_OTHER_NODE")); From 72f37f1b58158c6dbf5fed3aef2cd06d3ffc9fc5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 29 Oct 2018 23:21:26 -0700 Subject: [PATCH 245/342] Move path initialization to static initializer By the time the plugin is even loaded this should all exist --- ModuleManager/MMPatchLoader.cs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index fee3b2bd..749c170e 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -37,18 +37,18 @@ public class MMPatchLoader : LoadingSystem private static readonly Dictionary regexCache = new Dictionary(); - private static string cachePath; + private static readonly string cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); - internal static string techTreeFile; - internal static string techTreePath; + internal static readonly string techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); + internal static readonly string techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); - internal static string physicsFile; - internal static string physicsPath; - private static string defaultPhysicsPath; + internal static readonly string physicsFile = Path.Combine("GameData", "ModuleManager.Physics"); + internal static readonly string physicsPath = Path.Combine(KSPUtil.ApplicationRootPath, physicsFile); + private static readonly string defaultPhysicsPath = Path.Combine(KSPUtil.ApplicationRootPath, "Physics.cfg"); - internal static string partDatabasePath; + internal static readonly string partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); - private static string shaPath; + private static readonly string shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); private UrlDir.UrlFile physicsUrlFile; @@ -72,15 +72,6 @@ private void Awake() } Instance = this; - cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); - techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); - techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); - physicsFile = Path.Combine("GameData", "ModuleManager.Physics"); - physicsPath = Path.Combine(KSPUtil.ApplicationRootPath, physicsFile); - defaultPhysicsPath = Path.Combine(KSPUtil.ApplicationRootPath, "Physics.cfg"); - partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); - shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); - logger = new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger)); } From 122d4bfbfc8d4c32c84cc63a7da43430c0be1c32 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Oct 2018 01:04:50 -0700 Subject: [PATCH 246/342] Make sure cache also uses copy of game db Configs can be applied after in either case --- ModuleManager/MMPatchLoader.cs | 55 +++++++++++++++------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 749c170e..8c50fc7f 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -128,6 +128,8 @@ private IEnumerator ProcessPatch() #endif yield return null; + IEnumerable databaseConfigs = null; + if (!useCache) { IPatchProgress progress = new PatchProgress(logger); @@ -178,8 +180,6 @@ private IEnumerator ProcessPatch() logger.Info("Starting patch thread"); - IEnumerable databaseConfigs = null; - ITaskStatus patchThread = BackgroundTask.Start(delegate { databaseConfigs = applier.ApplyPatches(patchList); @@ -236,16 +236,6 @@ float updateTimeRemaining() yield break; } - foreach (UrlDir.UrlFile file in GameDatabase.Instance.root.AllConfigFiles) - { - file.configs.Clear(); - } - - foreach (IProtoUrlConfig protoConfig in databaseConfigs) - { - protoConfig.UrlFile.AddConfig(protoConfig.Node); - } - logger.Info("Done patching"); yield return null; @@ -286,7 +276,7 @@ float updateTimeRemaining() status = "Saving Cache"; logger.Info(status); yield return null; - CreateCache(progress.Counter.patchedNodes); + CreateCache(databaseConfigs, progress.Counter.patchedNodes); } StatusUpdate(progress); @@ -301,7 +291,17 @@ float updateTimeRemaining() status = "Loading from Cache"; logger.Info(status); yield return null; - LoadCache(); + databaseConfigs = LoadCache(); + } + + foreach (UrlDir.UrlFile file in GameDatabase.Instance.root.AllConfigFiles) + { + file.configs.Clear(); + } + + foreach (IProtoUrlConfig protoConfig in databaseConfigs) + { + protoConfig.UrlFile.AddConfig(protoConfig.Node); } logger.Info(status + "\n" + errors); @@ -476,7 +476,7 @@ private ConfigNode GetFileNode(ConfigNode shaConfigNode, string filename) } - private void CreateCache(int patchedNodeCount) + private void CreateCache(IEnumerable databaseConfigs, int patchedNodeCount) { ConfigNode shaConfigNode = new ConfigNode(); shaConfigNode.AddValue("SHA", configSha); @@ -488,13 +488,11 @@ private void CreateCache(int patchedNodeCount) cache.AddValue("patchedNodeCount", patchedNodeCount.ToString()); - foreach (UrlDir.UrlConfig config in GameDatabase.Instance.root.AllConfigs) + foreach (IProtoUrlConfig urlConfig in databaseConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); - node.AddValue("name", config.name); - node.AddValue("type", config.type); - node.AddValue("parentUrl", config.parent.url); - node.AddNode(config.config); + node.AddValue("parentUrl", urlConfig.UrlFile.url); + node.AddNode(urlConfig.Node); } foreach (var file in GameDatabase.Instance.root.AllConfigFiles) @@ -567,15 +565,8 @@ private void SaveModdedTechTree() techNode.Save(techTreePath); } - private void LoadCache() + private IEnumerable LoadCache() { - // Clear the config DB - foreach (UrlDir.UrlFile files in GameDatabase.Instance.root.AllConfigFiles) - { - files.configs.Clear(); - } - - // And then load all the cached configs ConfigNode cache = ConfigNode.Load(cachePath); if (cache.HasValue("patchedNodeCount") && int.TryParse(cache.GetValue("patchedNodeCount"), out int patchedNodeCount)) @@ -587,16 +578,16 @@ private void LoadCache() physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); gameDataDir.files.Add(physicsUrlFile); + List databaseConfigs = new List(cache.nodes.Count); + foreach (ConfigNode node in cache.nodes) { - string name = node.GetValue("name"); - string type = node.GetValue("type"); string parentUrl = node.GetValue("parentUrl"); UrlDir.UrlFile parent = GameDatabase.Instance.root.AllConfigFiles.FirstOrDefault(f => f.url == parentUrl); if (parent != null) { - parent.AddConfig(node.nodes[0]); + databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); } else { @@ -605,6 +596,8 @@ private void LoadCache() } progressFraction = 1; logger.Info("Cache Loaded"); + + return databaseConfigs; } private void StatusUpdate(IPatchProgress progress) From ea7b05a6358295f43f20d02aba4095e76e58fef3 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Oct 2018 22:48:55 -0700 Subject: [PATCH 247/342] Extract paths to their own class Forgot that the static initalizers would prevent tests from running --- ModuleManager/CustomConfigsManager.cs | 10 ++++++---- ModuleManager/FilePathRepository.cs | 21 +++++++++++++++++++++ ModuleManager/MMPatchLoader.cs | 15 ++------------- ModuleManager/ModuleManager.csproj | 1 + 4 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 ModuleManager/FilePathRepository.cs diff --git a/ModuleManager/CustomConfigsManager.cs b/ModuleManager/CustomConfigsManager.cs index d88f4ea9..b749ba2f 100644 --- a/ModuleManager/CustomConfigsManager.cs +++ b/ModuleManager/CustomConfigsManager.cs @@ -2,6 +2,8 @@ using System.IO; using UnityEngine; +using static ModuleManager.FilePathRepository; + namespace ModuleManager { [KSPAddon(KSPAddon.Startup.SpaceCentre, false)] @@ -9,17 +11,17 @@ public class CustomConfigsManager : MonoBehaviour { internal void Start() { - if (HighLogic.CurrentGame.Parameters.Career.TechTreeUrl != MMPatchLoader.techTreeFile && File.Exists(MMPatchLoader.techTreePath)) + if (HighLogic.CurrentGame.Parameters.Career.TechTreeUrl != techTreeFile && File.Exists(techTreePath)) { Log("Setting modded tech tree as the active one"); - HighLogic.CurrentGame.Parameters.Career.TechTreeUrl = MMPatchLoader.techTreeFile; + HighLogic.CurrentGame.Parameters.Career.TechTreeUrl = techTreeFile; } - if (PhysicsGlobals.PhysicsDatabaseFilename != MMPatchLoader.physicsFile && File.Exists(MMPatchLoader.physicsPath)) + if (PhysicsGlobals.PhysicsDatabaseFilename != physicsFile && File.Exists(physicsPath)) { Log("Setting modded physics as the active one"); - PhysicsGlobals.PhysicsDatabaseFilename = MMPatchLoader.physicsFile; + PhysicsGlobals.PhysicsDatabaseFilename = physicsFile; if (!PhysicsGlobals.Instance.LoadDatabase()) Log("Something went wrong while setting the active physics config."); diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs new file mode 100644 index 00000000..17ccee3d --- /dev/null +++ b/ModuleManager/FilePathRepository.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; + +namespace ModuleManager +{ + internal static class FilePathRepository + { + internal static readonly string cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); + + internal static readonly string techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); + internal static readonly string techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); + + internal static readonly string physicsFile = Path.Combine("GameData", "ModuleManager.Physics"); + internal static readonly string physicsPath = Path.Combine(KSPUtil.ApplicationRootPath, physicsFile); + internal static readonly string defaultPhysicsPath = Path.Combine(KSPUtil.ApplicationRootPath, "Physics.cfg"); + + internal static readonly string partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); + + internal static readonly string shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 8c50fc7f..02c56207 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -21,6 +21,8 @@ using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; +using static ModuleManager.FilePathRepository; + namespace ModuleManager { [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] @@ -37,19 +39,6 @@ public class MMPatchLoader : LoadingSystem private static readonly Dictionary regexCache = new Dictionary(); - private static readonly string cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); - - internal static readonly string techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); - internal static readonly string techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); - - internal static readonly string physicsFile = Path.Combine("GameData", "ModuleManager.Physics"); - internal static readonly string physicsPath = Path.Combine(KSPUtil.ApplicationRootPath, physicsFile); - private static readonly string defaultPhysicsPath = Path.Combine(KSPUtil.ApplicationRootPath, "Physics.cfg"); - - internal static readonly string partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); - - private static readonly string shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); - private UrlDir.UrlFile physicsUrlFile; private string configSha; diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 6de340a4..9996e052 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -58,6 +58,7 @@ + From d50fc9e2403b3860c842267b4ff3ef04d32c53e6 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Oct 2018 00:08:13 -0700 Subject: [PATCH 248/342] patch in background Patches are now applied on a separate thread to an isolated copy of the game database, then copied into the actual game database by post-patch runner. Post patch runner will wait for patched database if it isn't done yet. One consequence is that logging during patching can no longer be directed to the main log (it'll get mixed up with other messages). Now directed to /Logs/ModuleManager.log --- ModuleManager/MMPatchLoader.cs | 149 +++--------------- ModuleManager/MMPatchRunner.cs | 125 +++++++++++++++ ModuleManager/ModuleManager.cs | 64 ++++---- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/ModuleManagerPostPatch.cs | 32 +++- ModuleManager/PatchApplier.cs | 5 +- ModuleManager/Progress/IPatchProgress.cs | 4 + ModuleManager/Progress/PatchProgress.cs | 11 ++ ModuleManagerTests/PatchApplierTest.cs | 6 +- .../Progress/PatchProgressTest.cs | 35 ++++ 10 files changed, 268 insertions(+), 164 deletions(-) create mode 100644 ModuleManager/MMPatchRunner.cs diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 02c56207..3ffd20e8 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -14,9 +13,7 @@ using ModuleManager.Logging; using ModuleManager.Extensions; -using ModuleManager.Collections; using ModuleManager.Tags; -using ModuleManager.Threading; using ModuleManager.Patches; using ModuleManager.Progress; using NodeStack = ModuleManager.Collections.ImmutableStack; @@ -27,7 +24,7 @@ namespace ModuleManager { [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] - public class MMPatchLoader : LoadingSystem + public class MMPatchLoader { public string status = ""; @@ -35,8 +32,6 @@ public class MMPatchLoader : LoadingSystem public static bool keepPartDB = false; - private string activity = "Module Manager"; - private static readonly Dictionary regexCache = new Dictionary(); private UrlDir.UrlFile physicsUrlFile; @@ -48,59 +43,23 @@ public class MMPatchLoader : LoadingSystem private IBasicLogger logger; - private float progressFraction = 0; - - public static MMPatchLoader Instance { get; private set; } - - private void Awake() - { - if (Instance != null) - { - DestroyImmediate(this); - return; - } - Instance = this; - - logger = new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger)); - } - - private bool ready; - - public override bool IsReady() - { - return ready; - } - - public override float ProgressFraction() => progressFraction; - - public override string ProgressTitle() - { - return activity; - } - - public override void StartLoad() + public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) { - ready = false; - - // DB check used to track the now fixed TextureReplacer corruption - //checkValues(); - - StartCoroutine(ProcessPatch()); + PostPatchLoader.AddPostPatchCallback(callback); } - public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) + public MMPatchLoader(IBasicLogger logger) { - PostPatchLoader.AddPostPatchCallback(callback); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - private IEnumerator ProcessPatch() + public IEnumerable Run() { Stopwatch patchSw = new Stopwatch(); patchSw.Start(); status = "Checking Cache"; logger.Info(status); - yield return null; bool useCache = false; try @@ -115,7 +74,6 @@ private IEnumerator ProcessPatch() #if DEBUG //useCache = false; #endif - yield return null; IEnumerable databaseConfigs = null; @@ -126,8 +84,6 @@ private IEnumerator ProcessPatch() logger.Info(status); IEnumerable mods = ModListGenerator.GenerateModList(progress, logger); - yield return null; - // If we don't use the cache then it is best to clean the PartDatabase.cfg if (!keepPartDB && File.Exists(partDatabasePath)) File.Delete(partDatabasePath); @@ -139,8 +95,6 @@ private IEnumerator ProcessPatch() status = "Extracting patches"; logger.Info(status); - yield return null; - UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); INeedsChecker needsChecker = new NeedsChecker(mods, gameData, progress, logger); ITagListParser tagListParser = new TagListParser(progress); @@ -160,73 +114,30 @@ private IEnumerator ProcessPatch() status = "Applying patches"; logger.Info(status); - yield return null; - - MessageQueue logQueue = new MessageQueue(); - IBasicLogger patchLogger = new QueueLogger(logQueue); - IPatchProgress threadPatchProgress = new PatchProgress(progress, patchLogger); - PatchApplier applier = new PatchApplier(threadPatchProgress, patchLogger); + IPass currentPass = null; + float nextUpdate = Time.realtimeSinceStartup + yieldInterval; - logger.Info("Starting patch thread"); - - ITaskStatus patchThread = BackgroundTask.Start(delegate + progress.OnPassStarted.Add(delegate (IPass pass) { - databaseConfigs = applier.ApplyPatches(patchList); + currentPass = pass; + StatusUpdate(progress, currentPass.Name); }); - float nextYield = Time.realtimeSinceStartup + yieldInterval; - - float updateTimeRemaining() - { - float timeRemaining = nextYield - Time.realtimeSinceStartup; - if (timeRemaining < 0) - { - nextYield = Time.realtimeSinceStartup + yieldInterval; - StatusUpdate(progress); - activity = applier.Activity; - } - return timeRemaining; - } - - while (patchThread.IsRunning) + progress.OnPatchApplied.Add(delegate { - foreach (ILogMessage message in logQueue.TakeAll()) + if (Time.realtimeSinceStartup > nextUpdate) { - message.LogTo(logger); - - if (updateTimeRemaining() < 0) yield return null; + StatusUpdate(progress, currentPass.Name); + nextUpdate = Time.realtimeSinceStartup + yieldInterval; } + }); - float timeRemaining = updateTimeRemaining(); - if (timeRemaining > 0) System.Threading.Thread.Sleep((int)(timeRemaining * 1000)); - yield return null; - } + PatchApplier applier = new PatchApplier(progress, logger); + databaseConfigs = applier.ApplyPatches(patchList); StatusUpdate(progress); - activity = "ModuleManager - finishing up"; - yield return null; - - // Clear any log messages that might still be in the queue - foreach (ILogMessage message in logQueue.TakeAll()) - { - message.LogTo(logger); - } - - if (patchThread.IsExitedWithError) - { - progress.Exception("The patch runner threw an exception", patchThread.Exception); - FatalErrorHandler.HandleFatalError("The patch runner threw an exception"); - yield break; - } - if (databaseConfigs == null) - { - progress.Error("The patcher returned a null collection of configs"); - FatalErrorHandler.HandleFatalError("The patcher returned a null collection of configs"); - yield break; - } logger.Info("Done patching"); - yield return null; PurgeUnused(); @@ -264,7 +175,6 @@ float updateTimeRemaining() { status = "Saving Cache"; logger.Info(status); - yield return null; CreateCache(databaseConfigs, progress.Counter.patchedNodes); } @@ -279,26 +189,15 @@ float updateTimeRemaining() { status = "Loading from Cache"; logger.Info(status); - yield return null; databaseConfigs = LoadCache(); } - foreach (UrlDir.UrlFile file in GameDatabase.Instance.root.AllConfigFiles) - { - file.configs.Clear(); - } - - foreach (IProtoUrlConfig protoConfig in databaseConfigs) - { - protoConfig.UrlFile.AddConfig(protoConfig.Node); - } - logger.Info(status + "\n" + errors); patchSw.Stop(); logger.Info("Ran in " + ((float)patchSw.ElapsedMilliseconds / 1000).ToString("F3") + "s"); - ready = true; + return databaseConfigs; } private void LoadPhysicsConfig() @@ -583,17 +482,19 @@ private IEnumerable LoadCache() logger.Warning("Parent null for " + parentUrl); } } - progressFraction = 1; logger.Info("Cache Loaded"); return databaseConfigs; } - private void StatusUpdate(IPatchProgress progress) + private void StatusUpdate(IPatchProgress progress, string activity = null) { - progressFraction = progress.ProgressFraction; - status = "ModuleManager: " + progress.Counter.patchedNodes + " patch" + (progress.Counter.patchedNodes != 1 ? "es" : "") + " applied"; + if (progress.ProgressFraction < 1f - float.Epsilon) + status += " (" + progress.ProgressFraction * 100 + "%)"; + + if (activity != null) + status += "\n" + activity; if (progress.Counter.warnings > 0) status += ", found " + progress.Counter.warnings + " warning" + (progress.Counter.warnings != 1 ? "s" : "") + ""; diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs new file mode 100644 index 00000000..473053ad --- /dev/null +++ b/ModuleManager/MMPatchRunner.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using ModuleManager.Collections; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using ModuleManager.Threading; + +namespace ModuleManager +{ + public class MMPatchRunner + { + private const float TIME_TO_WAIT_FOR_LOGS = 0.05f; + + private readonly IBasicLogger kspLogger; + + public string Status { get; private set; } = ""; + public string Errors { get; private set; } = ""; + + public MMPatchRunner(IBasicLogger kspLogger) + { + this.kspLogger = kspLogger ?? throw new ArgumentNullException(nameof(kspLogger)); + } + + public IEnumerator Run() + { + PostPatchLoader.Instance.databaseConfigs = null; + + string logsDirPath = Path.Combine(KSPUtil.ApplicationRootPath, "Logs"); + if (!Directory.Exists(logsDirPath)) Directory.CreateDirectory(logsDirPath); + string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); + + kspLogger.Info("Patching started on a new thread, all output will be directed to " + logPath); + + MessageQueue kspLogQueue = new MessageQueue(); + MessageQueue mmLogQueue = new MessageQueue(); + bool logThreadExitFlag = false; + ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate + { + QueueLogger kspLogger = new QueueLogger(kspLogQueue); + using (StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create), kspLogger)) + { + while (!logThreadExitFlag) + { + float waitTargetTime = Time.realtimeSinceStartup + TIME_TO_WAIT_FOR_LOGS; + + foreach (ILogMessage message in mmLogQueue.TakeAll()) + { + message.LogTo(streamLogger); + } + + float timeRemaining = waitTargetTime - Time.realtimeSinceStartup; + if (timeRemaining > 0) + System.Threading.Thread.Sleep((int)(timeRemaining * 1000)); + } + + foreach (ILogMessage message in mmLogQueue.TakeAll()) + { + message.LogTo(streamLogger); + } + + streamLogger.Info("Done!"); + } + }); + + // Wait for game database to be initialized for the 2nd time + yield return null; + + IEnumerable databaseConfigs = null; + + MMPatchLoader patchLoader = new MMPatchLoader(new QueueLogger(mmLogQueue)); + + ITaskStatus patchingThreadStatus = BackgroundTask.Start(delegate + { + databaseConfigs = patchLoader.Run(); + }); + + while(true) + { + yield return null; + + if (!patchingThreadStatus.IsRunning) + logThreadExitFlag = true; + + Status = patchLoader.status; + Errors = patchLoader.errors; + + foreach (ILogMessage message in kspLogQueue.TakeAll()) + { + message.LogTo(kspLogger); + } + + if (!patchingThreadStatus.IsRunning && !loggingThreadStatus.IsRunning) break; + } + + foreach (ILogMessage message in kspLogQueue.TakeAll()) + { + message.LogTo(kspLogger); + } + + if (patchingThreadStatus.IsExitedWithError) + { + kspLogger.Exception("The patching thread threw an exception", patchingThreadStatus.Exception); + FatalErrorHandler.HandleFatalError("The patching thread threw an exception"); + } + + if (loggingThreadStatus.IsExitedWithError) + { + kspLogger.Exception("The logging thread threw an exception", loggingThreadStatus.Exception); + FatalErrorHandler.HandleFatalError("The logging thread threw an exception"); + } + + if (databaseConfigs == null) + { + kspLogger.Error("The patcher returned a null collection of configs"); + FatalErrorHandler.HandleFatalError("The patcher returned a null collection of configs"); + yield break; + } + + PostPatchLoader.Instance.databaseConfigs = databaseConfigs; + } + } +} diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 8ed242a4..1d62fb75 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -7,7 +7,9 @@ using System.Reflection; using TMPro; using UnityEngine; +using Debug = UnityEngine.Debug; using ModuleManager.Cats; +using ModuleManager.Logging; namespace ModuleManager { @@ -36,6 +38,8 @@ public class ModuleManager : MonoBehaviour private PopupDialog menu; + private MMPatchRunner patchRunner; + #endregion state private static bool loadedInScene; @@ -126,11 +130,11 @@ internal void Awake() GameObject aGameObject = new GameObject("ModuleManager"); DontDestroyOnLoad(aGameObject); - Log(string.Format("Adding patch loader to the loading screen {0}", list.Count)); - list.Insert(gameDatabaseIndex + 1, aGameObject.AddComponent()); - Log(string.Format("Adding post patch to the loading screen {0}", list.Count)); - list.Insert(gameDatabaseIndex + 2, aGameObject.AddComponent()); + list.Insert(gameDatabaseIndex + 1, aGameObject.AddComponent()); + + patchRunner = new MMPatchRunner(new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); + StartCoroutine(patchRunner.Run()); // Workaround for 1.6.0 Editor bug after a PartDatabase rebuild. if (Versioning.version_major == 1 && Versioning.version_minor == 6 && Versioning.Revision == 0) @@ -266,27 +270,31 @@ internal void Update() float offsetY = textPos; float h; - if (warning) - { - h = warning.text.Length > 0 ? warning.textBounds.size.y : 0; - offsetY = offsetY + h; - warning.rectTransform.localPosition = new Vector3(0, offsetY); - } - if (status) + if (patchRunner != null) { - status.text = MMPatchLoader.Instance.status; - h = status.text.Length > 0 ? status.textBounds.size.y: 0; - offsetY = offsetY + h; - status.transform.localPosition = new Vector3(0, offsetY); - } + if (warning) + { + h = warning.text.Length > 0 ? warning.textBounds.size.y : 0; + offsetY = offsetY + h; + warning.rectTransform.localPosition = new Vector3(0, offsetY); + } - if (errors) - { - errors.text = MMPatchLoader.Instance.errors; - h = errors.text.Length > 0 ? errors.textBounds.size.y: 0; - offsetY = offsetY + h; - errors.transform.localPosition = new Vector3(0, offsetY); + if (status) + { + status.text = patchRunner.Status; + h = status.text.Length > 0 ? status.textBounds.size.y : 0; + offsetY = offsetY + h; + status.transform.localPosition = new Vector3(0, offsetY); + } + + if (errors) + { + errors.text = patchRunner.Errors; + h = errors.text.Length > 0 ? errors.textBounds.size.y : 0; + offsetY = offsetY + h; + errors.transform.localPosition = new Vector3(0, offsetY); + } } if (reloading) @@ -294,8 +302,6 @@ internal void Update() float percent = 0; if (!GameDatabase.Instance.IsReady()) percent = GameDatabase.Instance.ProgressFraction(); - else if (!MMPatchLoader.Instance.IsReady()) - percent = 1f + MMPatchLoader.Instance.ProgressFraction(); else if (!PartLoader.Instance.IsReady()) percent = 2f + PartLoader.Instance.ProgressFraction(); @@ -328,13 +334,13 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) GameDatabase.Instance.Recompile = true; GameDatabase.Instance.StartLoad(); - // wait for it to finish - while (!GameDatabase.Instance.IsReady()) - yield return null; + yield return null; - MMPatchLoader.Instance.StartLoad(); + patchRunner = new MMPatchRunner(new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); + StartCoroutine(patchRunner.Run()); - while (!MMPatchLoader.Instance.IsReady()) + // wait for it to finish + while (!GameDatabase.Instance.IsReady()) yield return null; PostPatchLoader.Instance.StartLoad(); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 9996e052..d6e367b0 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -56,6 +56,7 @@ + diff --git a/ModuleManager/ModuleManagerPostPatch.cs b/ModuleManager/ModuleManagerPostPatch.cs index 1f4da965..00c0f74c 100644 --- a/ModuleManager/ModuleManagerPostPatch.cs +++ b/ModuleManager/ModuleManagerPostPatch.cs @@ -15,6 +15,8 @@ public class PostPatchLoader : LoadingSystem { public static PostPatchLoader Instance { get; private set; } + public IEnumerable databaseConfigs = null; + private static readonly List postPatchCallbacks = new List(); private readonly IBasicLogger logger = new ModLogger("ModuleManager", new UnityLogger(UnityEngine.Debug.unityLogger)); @@ -53,8 +55,30 @@ public override void StartLoad() private IEnumerator Run() { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); + Stopwatch waitTimer = new Stopwatch(); + waitTimer.Start(); + + while (databaseConfigs == null) yield return null; + + waitTimer.Stop(); + logger.Info("Waited " + ((float)waitTimer.ElapsedMilliseconds / 1000).ToString("F3") + "s for patching to finish"); + + Stopwatch postPatchTimer = new Stopwatch(); + postPatchTimer.Start(); + + logger.Info("Applying patched game database"); + + foreach (UrlDir.UrlFile file in GameDatabase.Instance.root.AllConfigFiles) + { + file.configs.Clear(); + } + + foreach (IProtoUrlConfig protoConfig in databaseConfigs) + { + protoConfig.UrlFile.AddConfig(protoConfig.Node); + } + + databaseConfigs = null; #if DEBUG InGameTestRunner testRunner = new InGameTestRunner(logger); @@ -145,8 +169,8 @@ private IEnumerator Run() if (ModuleManager.dumpPostPatch) ModuleManager.OutputAllConfigs(); - stopwatch.Stop(); - logger.Info("Post patch ran in " + ((float)stopwatch.ElapsedMilliseconds / 1000).ToString("F3") + "s"); + postPatchTimer.Stop(); + logger.Info("Post patch ran in " + ((float)postPatchTimer.ElapsedMilliseconds / 1000).ToString("F3") + "s"); ready = true; } diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 72e4fbd0..94d16e03 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -12,8 +12,6 @@ public class PatchApplier private readonly IBasicLogger logger; private readonly IPatchProgress progress; - public string Activity { get; private set; } - public PatchApplier(IPatchProgress progress, IBasicLogger logger) { this.progress = progress ?? throw new ArgumentNullException(nameof(progress)); @@ -36,8 +34,7 @@ public IEnumerable ApplyPatches(IEnumerable patches) private void ApplyPatches(LinkedList databaseConfigs, IPass pass) { - logger.Info(pass.Name + " pass"); - Activity = "ModuleManager " + pass.Name; + progress.PassStarted(pass); foreach (IPatch patch in pass) { diff --git a/ModuleManager/Progress/IPatchProgress.cs b/ModuleManager/Progress/IPatchProgress.cs index d1b39cae..46bcb2ad 100644 --- a/ModuleManager/Progress/IPatchProgress.cs +++ b/ModuleManager/Progress/IPatchProgress.cs @@ -8,6 +8,9 @@ public interface IPatchProgress float ProgressFraction { get; } + EventVoid OnPatchApplied { get; } + EventData OnPassStarted { get; } + void Warning(UrlDir.UrlConfig url, string message); void Error(UrlDir.UrlConfig url, string message); void Error(string message); @@ -24,5 +27,6 @@ public interface IPatchProgress void ApplyingUpdate(IUrlConfigIdentifier original, UrlDir.UrlConfig patch); void PatchAdded(); void PatchApplied(); + void PassStarted(IPass pass); } } diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index d61c35eb..b1d92721 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -21,6 +21,9 @@ public float ProgressFraction } } + public EventVoid OnPatchApplied { get; } = new EventVoid("OnPatchApplied"); + public EventData OnPassStarted { get; } = new EventData("OnPassStarted"); + public PatchProgress(IBasicLogger logger) { this.logger = logger; @@ -38,6 +41,13 @@ public void PatchAdded() Counter.totalPatches.Increment(); } + public void PassStarted(IPass pass) + { + if (pass == null) throw new ArgumentNullException(nameof(pass)); + logger.Info(pass.Name + " pass"); + OnPassStarted.Fire(pass); + } + public void ApplyingUpdate(IUrlConfigIdentifier original, UrlDir.UrlConfig patch) { logger.Info($"Applying update {patch.SafeUrl()} to {original.FullUrl}"); @@ -59,6 +69,7 @@ public void ApplyingDelete(IUrlConfigIdentifier original, UrlDir.UrlConfig patch public void PatchApplied() { Counter.appliedPatches.Increment(); + OnPatchApplied.Fire(); } public void NeedsUnsatisfiedRoot(UrlDir.UrlConfig url) diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index 81011b97..7cd8f7c9 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -85,21 +85,21 @@ public void TestApplyPatches() Received.InOrder(delegate { - logger.Log(LogType.Log, ":PASS1 pass"); + progress.PassStarted(pass1); patches[0].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); patches[1].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); patches[2].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); - logger.Log(LogType.Log, ":PASS2 pass"); + progress.PassStarted(pass2); patches[3].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); patches[4].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); patches[5].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); - logger.Log(LogType.Log, ":PASS3 pass"); + progress.PassStarted(pass3); patches[6].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); patches[7].Apply(databaseConfigs, progress, logger); diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index c7f448e1..ee61fd46 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -110,11 +110,15 @@ public void TesApplyingDelete() [Fact] public void TestPatchApplied() { + int eventCounter = 0; + progress.OnPatchApplied.Add(() => eventCounter++); Assert.Equal(0, progress.Counter.appliedPatches); progress.PatchApplied(); Assert.Equal(1, progress.Counter.appliedPatches); + Assert.Equal(1, eventCounter); progress.PatchApplied(); Assert.Equal(2, progress.Counter.appliedPatches); + Assert.Equal(2, eventCounter); } [Fact] @@ -219,6 +223,37 @@ public void TestNeedsUnsatisfiedAfter() logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); } + [Fact] + public void TestStartingPass() + { + EventData.OnEvent onEvent = Substitute.For.OnEvent>(); + progress.OnPassStarted.Add(onEvent); + IPass pass1 = Substitute.For(); + pass1.Name.Returns(":SOME_PASS"); + + progress.PassStarted(pass1); + + logger.Received().Log(LogType.Log, ":SOME_PASS pass"); + onEvent.Received()(pass1); + } + + [Fact] + public void TestStartingPass__NullArgument() + { + EventData.OnEvent onEvent = Substitute.For.OnEvent>(); + progress.OnPassStarted.Add(onEvent); + + ArgumentNullException ex = Assert.Throws(delegate + { + progress.PassStarted(null); + }); + + Assert.Equal("pass", ex.ParamName); + + logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); + onEvent.DidNotReceiveWithAnyArgs()(null); + } + [Fact] public void TestWarning() { From 09b1eff0444f3ecdb1e6b312b1f10906e1ef2b73 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 1 Nov 2018 23:02:19 -0700 Subject: [PATCH 249/342] Remove PurgeUnused Hasn't been necessary for a while --- ModuleManager/MMPatchLoader.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 3ffd20e8..eb9a7f42 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -139,8 +139,6 @@ public IEnumerable Run() logger.Info("Done patching"); - PurgeUnused(); - #endregion Applying patches #region Saving Cache @@ -506,17 +504,6 @@ private void StatusUpdate(IPatchProgress progress, string activity = null) status += ", encountered " + progress.Counter.exceptions + " exception" + (progress.Counter.exceptions != 1 ? "s" : "") + ""; } - private static void PurgeUnused() - { - foreach (UrlDir.UrlConfig mod in GameDatabase.Instance.root.AllConfigs.ToArray()) - { - string name = mod.type.RemoveWS(); - - if (CommandParser.Parse(name, out name) != Command.Insert) - mod.parent.configs.Remove(mod); - } - } - #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 From 39e4157f86bcce19eafb2d2adbc0fc5b64f520ff Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 1 Nov 2018 23:07:51 -0700 Subject: [PATCH 250/342] Put log paths in path repository --- ModuleManager/FilePathRepository.cs | 3 +++ ModuleManager/MMPatchRunner.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index 17ccee3d..24cac366 100644 --- a/ModuleManager/FilePathRepository.cs +++ b/ModuleManager/FilePathRepository.cs @@ -17,5 +17,8 @@ internal static class FilePathRepository internal static readonly string partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); internal static readonly string shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); + + internal static readonly string logsDirPath = Path.Combine(KSPUtil.ApplicationRootPath, "Logs"); + internal static readonly string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); } } diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 473053ad..47b41371 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -8,6 +8,8 @@ using ModuleManager.Logging; using ModuleManager.Threading; +using static ModuleManager.FilePathRepository; + namespace ModuleManager { public class MMPatchRunner @@ -28,9 +30,7 @@ public IEnumerator Run() { PostPatchLoader.Instance.databaseConfigs = null; - string logsDirPath = Path.Combine(KSPUtil.ApplicationRootPath, "Logs"); if (!Directory.Exists(logsDirPath)) Directory.CreateDirectory(logsDirPath); - string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); kspLogger.Info("Patching started on a new thread, all output will be directed to " + logPath); From de6b621e8e8bfcdbd9ec917f0fcd9404d1967579 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 1 Nov 2018 23:09:17 -0700 Subject: [PATCH 251/342] Rename file to reflect class name --- ModuleManager/ModuleManager.csproj | 2 +- ModuleManager/{ModuleManagerPostPatch.cs => PostPatchLoader.cs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename ModuleManager/{ModuleManagerPostPatch.cs => PostPatchLoader.cs} (100%) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index d6e367b0..c22076f6 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -60,7 +60,7 @@ - + diff --git a/ModuleManager/ModuleManagerPostPatch.cs b/ModuleManager/PostPatchLoader.cs similarity index 100% rename from ModuleManager/ModuleManagerPostPatch.cs rename to ModuleManager/PostPatchLoader.cs From 6ed6702fcf105168da1552667e2d6cf5a8e5dc2a Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 1 Nov 2018 23:35:54 -0700 Subject: [PATCH 252/342] Dump ModuleManager log to main log after patching Makes it easier to debug people's issues --- ModuleManager/PostPatchLoader.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index 00c0f74c..630b3818 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -7,6 +8,8 @@ using ModuleManager.Extensions; using ModuleManager.Logging; +using static ModuleManager.FilePathRepository; + namespace ModuleManager { public delegate void ModuleManagerPostPatchCallback(); @@ -80,6 +83,20 @@ private IEnumerator Run() databaseConfigs = null; + yield return null; + + if (File.Exists(logPath)) + { + logger.Info("Dumping ModuleManager log to main log"); + logger.Info("\n#### BEGIN MODULEMANAGER LOG ####\n\n\n" + File.ReadAllText(logPath) + "\n\n\n#### END MODULEMANAGER LOG ####"); + } + else + { + logger.Error("ModuleManager log does not exist: " + logPath); + } + + yield return null; + #if DEBUG InGameTestRunner testRunner = new InGameTestRunner(logger); testRunner.RunTestCases(GameDatabase.Instance.root); From 1589e0708914af3889854411f44e89ee86c78b02 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 26 Nov 2018 23:16:01 -0800 Subject: [PATCH 253/342] Don't count insert nodes as patches As far as progress is concerned, these take much less time to apply than patches, and are often less numerous. This can lead to weird completion percentages. --- ModuleManager/PatchApplier.cs | 2 +- ModuleManager/PatchList.cs | 2 +- ModuleManager/Patches/CopyPatch.cs | 1 + ModuleManager/Patches/DeletePatch.cs | 1 + ModuleManager/Patches/EditPatch.cs | 1 + ModuleManager/Patches/IPatch.cs | 1 + ModuleManager/Patches/InsertPatch.cs | 1 + ModuleManagerTests/PatchApplierTest.cs | 13 ++++++--- ModuleManagerTests/PatchListTest.cs | 27 ++++++++++++++++++- ModuleManagerTests/Patches/CopyPatchTest.cs | 7 +++++ ModuleManagerTests/Patches/DeletePatchTest.cs | 7 +++++ ModuleManagerTests/Patches/EditPatchTest.cs | 7 +++++ ModuleManagerTests/Patches/InsertPatchTest.cs | 7 +++++ 13 files changed, 71 insertions(+), 6 deletions(-) diff --git a/ModuleManager/PatchApplier.cs b/ModuleManager/PatchApplier.cs index 94d16e03..adaade88 100644 --- a/ModuleManager/PatchApplier.cs +++ b/ModuleManager/PatchApplier.cs @@ -41,7 +41,7 @@ private void ApplyPatches(LinkedList databaseConfigs, IPass pas try { patch.Apply(databaseConfigs, progress, logger); - progress.PatchApplied(); + if (patch.CountsAsPatch) progress.PatchApplied(); } catch (Exception e) { diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index 892b6843..9716de22 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -126,7 +126,7 @@ public PatchList(IEnumerable modList, IEnumerable patches, IPatc throw new NotImplementedException("Don't know what to do with pass specifier: " + patch.PassSpecifier.Descriptor); } - progress.PatchAdded(); + if (patch.CountsAsPatch) progress.PatchAdded(); } } diff --git a/ModuleManager/Patches/CopyPatch.cs b/ModuleManager/Patches/CopyPatch.cs index 05241973..45ee09fe 100644 --- a/ModuleManager/Patches/CopyPatch.cs +++ b/ModuleManager/Patches/CopyPatch.cs @@ -13,6 +13,7 @@ public class CopyPatch : IPatch public UrlDir.UrlConfig UrlConfig { get; } public INodeMatcher NodeMatcher { get; } public IPassSpecifier PassSpecifier { get; } + public bool CountsAsPatch => true; public CopyPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier) { diff --git a/ModuleManager/Patches/DeletePatch.cs b/ModuleManager/Patches/DeletePatch.cs index fd465c9a..46d46a94 100644 --- a/ModuleManager/Patches/DeletePatch.cs +++ b/ModuleManager/Patches/DeletePatch.cs @@ -12,6 +12,7 @@ public class DeletePatch : IPatch public UrlDir.UrlConfig UrlConfig { get; } public INodeMatcher NodeMatcher { get; } public IPassSpecifier PassSpecifier { get; } + public bool CountsAsPatch => true; public DeletePatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier) { diff --git a/ModuleManager/Patches/EditPatch.cs b/ModuleManager/Patches/EditPatch.cs index 501144e9..91613879 100644 --- a/ModuleManager/Patches/EditPatch.cs +++ b/ModuleManager/Patches/EditPatch.cs @@ -15,6 +15,7 @@ public class EditPatch : IPatch public UrlDir.UrlConfig UrlConfig { get; } public INodeMatcher NodeMatcher { get; } public IPassSpecifier PassSpecifier { get; } + public bool CountsAsPatch => true; public EditPatch(UrlDir.UrlConfig urlConfig, INodeMatcher nodeMatcher, IPassSpecifier passSpecifier) { diff --git a/ModuleManager/Patches/IPatch.cs b/ModuleManager/Patches/IPatch.cs index 032ce6e1..a877ea80 100644 --- a/ModuleManager/Patches/IPatch.cs +++ b/ModuleManager/Patches/IPatch.cs @@ -10,6 +10,7 @@ public interface IPatch { UrlDir.UrlConfig UrlConfig { get; } IPassSpecifier PassSpecifier { get; } + bool CountsAsPatch { get; } void Apply(LinkedList configs, IPatchProgress progress, IBasicLogger logger); } } diff --git a/ModuleManager/Patches/InsertPatch.cs b/ModuleManager/Patches/InsertPatch.cs index 9f3c8a50..c5f2c17f 100644 --- a/ModuleManager/Patches/InsertPatch.cs +++ b/ModuleManager/Patches/InsertPatch.cs @@ -12,6 +12,7 @@ public class InsertPatch : IPatch public UrlDir.UrlConfig UrlConfig { get; } public string NodeType { get; } public IPassSpecifier PassSpecifier { get; } + public bool CountsAsPatch => false; public InsertPatch(UrlDir.UrlConfig urlConfig, string nodeType, IPassSpecifier passSpecifier) { diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index 7cd8f7c9..d7fb0eb1 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -67,6 +67,16 @@ public void TestApplyPatches() patches[i] = Substitute.For(); } + patches[0].CountsAsPatch.Returns(false); + patches[1].CountsAsPatch.Returns(false); + patches[2].CountsAsPatch.Returns(false); + patches[3].CountsAsPatch.Returns(true); + patches[4].CountsAsPatch.Returns(true); + patches[5].CountsAsPatch.Returns(true); + patches[6].CountsAsPatch.Returns(true); + patches[7].CountsAsPatch.Returns(true); + patches[8].CountsAsPatch.Returns(true); + pass1.GetEnumerator().Returns(new ArrayEnumerator(patches[0], patches[1], patches[2])); pass2.GetEnumerator().Returns(new ArrayEnumerator(patches[3], patches[4], patches[5])); pass3.GetEnumerator().Returns(new ArrayEnumerator(patches[6], patches[7], patches[8])); @@ -87,11 +97,8 @@ public void TestApplyPatches() { progress.PassStarted(pass1); patches[0].Apply(databaseConfigs, progress, logger); - progress.PatchApplied(); patches[1].Apply(databaseConfigs, progress, logger); - progress.PatchApplied(); patches[2].Apply(databaseConfigs, progress, logger); - progress.PatchApplied(); progress.PassStarted(pass2); patches[3].Apply(databaseConfigs, progress, logger); progress.PatchApplied(); diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index 8e66f0b3..09b1c1dc 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -142,6 +142,31 @@ public void Test__Lifecycle() patches[22].PassSpecifier.Returns(new FinalPassSpecifier()); patches[23].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[00].CountsAsPatch.Returns(false); + patches[01].CountsAsPatch.Returns(false); + patches[02].CountsAsPatch.Returns(true); + patches[03].CountsAsPatch.Returns(true); + patches[04].CountsAsPatch.Returns(true); + patches[05].CountsAsPatch.Returns(true); + patches[06].CountsAsPatch.Returns(true); + patches[07].CountsAsPatch.Returns(true); + patches[08].CountsAsPatch.Returns(true); + patches[09].CountsAsPatch.Returns(true); + patches[10].CountsAsPatch.Returns(true); + patches[11].CountsAsPatch.Returns(true); + patches[12].CountsAsPatch.Returns(true); + patches[13].CountsAsPatch.Returns(true); + patches[14].CountsAsPatch.Returns(true); + patches[15].CountsAsPatch.Returns(true); + patches[16].CountsAsPatch.Returns(true); + patches[17].CountsAsPatch.Returns(true); + patches[18].CountsAsPatch.Returns(true); + patches[19].CountsAsPatch.Returns(true); + patches[20].CountsAsPatch.Returns(true); + patches[21].CountsAsPatch.Returns(true); + patches[22].CountsAsPatch.Returns(true); + patches[23].CountsAsPatch.Returns(true); + IPatchProgress progress = Substitute.For(); PatchList patchList = new PatchList(new[] { "mod1", "mod2" }, patches, progress); @@ -186,7 +211,7 @@ public void Test__Lifecycle() Assert.Equal(":FINAL", passes[11].Name); Assert.Equal(new[] { patches[22], patches[23] }, passes[11]); - progress.Received(patches.Length).PatchAdded(); + progress.Received(22).PatchAdded(); } } } diff --git a/ModuleManagerTests/Patches/CopyPatchTest.cs b/ModuleManagerTests/Patches/CopyPatchTest.cs index c0e8e14a..ee6443dd 100644 --- a/ModuleManagerTests/Patches/CopyPatchTest.cs +++ b/ModuleManagerTests/Patches/CopyPatchTest.cs @@ -74,6 +74,13 @@ public void TestPassSpecifier() Assert.Same(passSpecifier, patch.PassSpecifier); } + [Fact] + public void TestCountsAsPatch() + { + CopyPatch patch = new CopyPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); + Assert.True(patch.CountsAsPatch); + } + [Fact] public void TestApply() { diff --git a/ModuleManagerTests/Patches/DeletePatchTest.cs b/ModuleManagerTests/Patches/DeletePatchTest.cs index 1cfa950d..f45916fa 100644 --- a/ModuleManagerTests/Patches/DeletePatchTest.cs +++ b/ModuleManagerTests/Patches/DeletePatchTest.cs @@ -73,6 +73,13 @@ public void TestPassSpecifier() Assert.Same(passSpecifier, patch.PassSpecifier); } + [Fact] + public void TestCountsAsPatch() + { + DeletePatch patch = new DeletePatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); + Assert.True(patch.CountsAsPatch); + } + [Fact] public void TestApply() { diff --git a/ModuleManagerTests/Patches/EditPatchTest.cs b/ModuleManagerTests/Patches/EditPatchTest.cs index 7ce5f6a9..50efd3b1 100644 --- a/ModuleManagerTests/Patches/EditPatchTest.cs +++ b/ModuleManagerTests/Patches/EditPatchTest.cs @@ -75,6 +75,13 @@ public void TestPassSpecifier() Assert.Same(passSpecifier, patch.PassSpecifier); } + [Fact] + public void TestCountsAsPatch() + { + EditPatch patch = new EditPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), Substitute.For(), Substitute.For()); + Assert.True(patch.CountsAsPatch); + } + [Fact] public void TestApply() { diff --git a/ModuleManagerTests/Patches/InsertPatchTest.cs b/ModuleManagerTests/Patches/InsertPatchTest.cs index b9944a9d..f1e134a4 100644 --- a/ModuleManagerTests/Patches/InsertPatchTest.cs +++ b/ModuleManagerTests/Patches/InsertPatchTest.cs @@ -73,6 +73,13 @@ public void TestPassSpecifier() Assert.Same(passSpecifier, patch.PassSpecifier); } + + [Fact] + public void TestCountsAsPatch() + { + InsertPatch patch = new InsertPatch(UrlBuilder.CreateConfig("abc/def", new ConfigNode()), "A_NODE", Substitute.For()); + Assert.False(patch.CountsAsPatch); + } [Fact] public void TestApply() From c2424cb2254dbaa3bdd45e2defcfb20c40116e26 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 26 Nov 2018 23:25:00 -0800 Subject: [PATCH 254/342] Only display whole percentages --- ModuleManager/MMPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index eb9a7f42..6acc6740 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -489,7 +489,7 @@ private void StatusUpdate(IPatchProgress progress, string activity = null) { status = "ModuleManager: " + progress.Counter.patchedNodes + " patch" + (progress.Counter.patchedNodes != 1 ? "es" : "") + " applied"; if (progress.ProgressFraction < 1f - float.Epsilon) - status += " (" + progress.ProgressFraction * 100 + "%)"; + status += " (" + progress.ProgressFraction.ToString("P0") + ")"; if (activity != null) status += "\n" + activity; From aeca3350c3960f3b87489f73e120c86c8c3f21de Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 26 Nov 2018 23:30:54 -0800 Subject: [PATCH 255/342] Add subdir under Logs Makes it consistent with Kopernicus --- ModuleManager/FilePathRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index 24cac366..52f94573 100644 --- a/ModuleManager/FilePathRepository.cs +++ b/ModuleManager/FilePathRepository.cs @@ -18,7 +18,7 @@ internal static class FilePathRepository internal static readonly string shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); - internal static readonly string logsDirPath = Path.Combine(KSPUtil.ApplicationRootPath, "Logs"); + internal static readonly string logsDirPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "Logs"), "ModuleManager"); internal static readonly string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); } } From ed78083e1fd679a0c23ffe272b5fa1863aefd893 Mon Sep 17 00:00:00 2001 From: blowfish Date: Wed, 19 Dec 2018 21:44:12 -0800 Subject: [PATCH 256/342] Replace reloading screen messages with dialog --- ModuleManager/ModuleManager.cs | 92 ++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 22 deletions(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 1d62fb75..ac92cddc 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -20,8 +20,6 @@ public class ModuleManager : MonoBehaviour private bool inRnDCenter; - private bool reloading; - public bool showUI = false; private Rect windowPos = new Rect(80f, 60f, 240f, 40f); @@ -296,19 +294,6 @@ internal void Update() errors.transform.localPosition = new Vector3(0, offsetY); } } - - if (reloading) - { - float percent = 0; - if (!GameDatabase.Instance.IsReady()) - percent = GameDatabase.Instance.ProgressFraction(); - else if (!PartLoader.Instance.IsReady()) - percent = 2f + PartLoader.Instance.ProgressFraction(); - - int intPercent = Mathf.CeilToInt(percent * 100f / 3f); - ScreenMessages.PostScreenMessage("Database reloading " + intPercent + "%", Time.deltaTime, - ScreenMessageStyle.UPPER_CENTER); - } } #region GUI stuff. @@ -323,20 +308,83 @@ public static bool IsABadIdea() private IEnumerator DataBaseReloadWithMM(bool dump = false) { - reloading = true; - QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; - ScreenMessages.PostScreenMessage("Database reloading started", 1, ScreenMessageStyle.UPPER_CENTER); + patchRunner = new MMPatchRunner(new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); + + float totalLoadWeight = GameDatabase.Instance.LoadWeight() + PartLoader.Instance.LoadWeight(); + bool startedReload = false; + + UISkinDef skinDef = HighLogic.UISkin; + UIStyle centeredTextStyle = new UIStyle(skinDef.label) + { + alignment = TextAnchor.UpperCenter + }; + + PopupDialog reloadingDialog = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + new MultiOptionDialog( + "ModuleManagerReloading", + "", + "ModuleManager - Reloading Database", + skinDef, + new Rect(0.5f, 0.5f, 600f, 60f), + new DialogGUIFlexibleSpace(), + new DialogGUIVerticalLayout( + new DialogGUIFlexibleSpace(), + new DialogGUILabel(delegate () + { + float progressFraction; + if (!startedReload) + { + progressFraction = 0f; + } + else if (!GameDatabase.Instance.IsReady()) + { + progressFraction = GameDatabase.Instance.ProgressFraction() * GameDatabase.Instance.LoadWeight(); + progressFraction /= totalLoadWeight; + } + else if (!PartLoader.Instance.IsReady()) + { + progressFraction = GameDatabase.Instance.LoadWeight() + (PartLoader.Instance.ProgressFraction() * GameDatabase.Instance.LoadWeight()); + progressFraction /= totalLoadWeight; + } + else + { + progressFraction = 1f; + } + + return $"Overall progress: {progressFraction:P0}"; + }, centeredTextStyle, expandW: true), + new DialogGUILabel(delegate () + { + if (!startedReload) + return "Starting"; + else if (!GameDatabase.Instance.IsReady()) + return GameDatabase.Instance.ProgressTitle(); + else if (!PostPatchLoader.Instance.IsReady()) + return PostPatchLoader.Instance.ProgressTitle(); + else if (!PartLoader.Instance.IsReady()) + return PartLoader.Instance.ProgressTitle(); + else + return ""; + }), + new DialogGUISpace(5f), + new DialogGUILabel(() => patchRunner.Status) + ) + ), + false, + skinDef); + yield return null; GameDatabase.Instance.Recompile = true; GameDatabase.Instance.StartLoad(); - yield return null; + startedReload = true; - patchRunner = new MMPatchRunner(new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); + yield return null; StartCoroutine(patchRunner.Run()); // wait for it to finish @@ -372,8 +420,8 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) QualitySettings.vSyncCount = GameSettings.SYNC_VBL; Application.targetFrameRate = GameSettings.FRAMERATE_LIMIT; - reloading = false; - ScreenMessages.PostScreenMessage("Database reloading finished", 1, ScreenMessageStyle.UPPER_CENTER); + + reloadingDialog.Dismiss(); } public static void OutputAllConfigs() From 52d8b18040d915575e643cf6d8b5cc039f779fb0 Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Mon, 24 Dec 2018 22:28:54 -0800 Subject: [PATCH 257/342] add LogSplitter directs logs to two other loggers --- ModuleManager/Logging/LogSplitter.cs | 29 ++++++++++ ModuleManager/ModuleManager.csproj | 1 + ModuleManagerTests/Logging/LogSplitterTest.cs | 56 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 4 files changed, 87 insertions(+) create mode 100644 ModuleManager/Logging/LogSplitter.cs create mode 100644 ModuleManagerTests/Logging/LogSplitterTest.cs diff --git a/ModuleManager/Logging/LogSplitter.cs b/ModuleManager/Logging/LogSplitter.cs new file mode 100644 index 00000000..53712f20 --- /dev/null +++ b/ModuleManager/Logging/LogSplitter.cs @@ -0,0 +1,29 @@ +using System; +using UnityEngine; + +namespace ModuleManager.Logging +{ + public class LogSplitter : IBasicLogger + { + private readonly IBasicLogger logger1; + private readonly IBasicLogger logger2; + + public LogSplitter(IBasicLogger logger1, IBasicLogger logger2) + { + this.logger1 = logger1 ?? throw new ArgumentNullException(nameof(logger1)); + this.logger2 = logger2 ?? throw new ArgumentNullException(nameof(logger2)); + } + + public void Log(LogType logType, string message) + { + logger1.Log(logType, message); + logger2.Log(logType, message); + } + + public void Exception(string message, Exception exception) + { + logger1.Exception(message, exception); + logger2.Exception(message, exception); + } + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index c22076f6..51550b90 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -50,6 +50,7 @@ + diff --git a/ModuleManagerTests/Logging/LogSplitterTest.cs b/ModuleManagerTests/Logging/LogSplitterTest.cs new file mode 100644 index 00000000..2375a3a3 --- /dev/null +++ b/ModuleManagerTests/Logging/LogSplitterTest.cs @@ -0,0 +1,56 @@ +using System; +using Xunit; +using NSubstitute; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class LogSplitterTest + { + [Fact] + public void TestConstructor__Logger1Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogSplitter(null, Substitute.For()); + }); + + Assert.Equal("logger1", ex.ParamName); + } + + [Fact] + public void TestConstructor__Logger2Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogSplitter(Substitute.For(), null); + }); + + Assert.Equal("logger2", ex.ParamName); + } + + [Fact] + public void TestLog() + { + IBasicLogger logger1 = Substitute.For(); + IBasicLogger logger2 = Substitute.For(); + LogSplitter logSplitter = new LogSplitter(logger1, logger2); + logSplitter.Log(LogType.Log, "some stuff"); + logger1.Received().Log(LogType.Log, "some stuff"); + logger2.Received().Log(LogType.Log, "some stuff"); + } + + [Fact] + public void TestException() + { + IBasicLogger logger1 = Substitute.For(); + IBasicLogger logger2 = Substitute.For(); + LogSplitter logSplitter = new LogSplitter(logger1, logger2); + Exception ex = new Exception(); + logSplitter.Exception("some stuff", ex); + logger1.Received().Exception("some stuff", ex); + logger2.Received().Exception("some stuff", ex); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index f07b5c07..f5d9a47e 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -85,6 +85,7 @@ + From 714500341d17e5a0fccba3baa61b2ac6105e62f9 Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Mon, 24 Dec 2018 22:31:58 -0800 Subject: [PATCH 258/342] Save patch log and dump when loading from cache This ensures that modders have access to a full history of what ModuleManager did even when the log was taken from a run where it loaded from cache --- ModuleManager/FilePathRepository.cs | 1 + ModuleManager/MMPatchLoader.cs | 96 +++++++++++++++++++++++++---- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index 52f94573..4ba34321 100644 --- a/ModuleManager/FilePathRepository.cs +++ b/ModuleManager/FilePathRepository.cs @@ -20,5 +20,6 @@ internal static class FilePathRepository internal static readonly string logsDirPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "Logs"), "ModuleManager"); internal static readonly string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); + internal static readonly string patchLogPath = Path.Combine(logsDirPath, "MMPatch.log"); } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 6acc6740..49d6e326 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -11,8 +11,10 @@ using UnityEngine; using Debug = UnityEngine.Debug; +using ModuleManager.Collections; using ModuleManager.Logging; using ModuleManager.Extensions; +using ModuleManager.Threading; using ModuleManager.Tags; using ModuleManager.Patches; using ModuleManager.Progress; @@ -41,6 +43,8 @@ public class MMPatchLoader private const float yieldInterval = 1f/30f; // Patch at ~30fps + private const float TIME_TO_WAIT_FOR_LOGS = 0.05f; + private IBasicLogger logger; public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) @@ -79,10 +83,53 @@ public IEnumerable Run() if (!useCache) { - IPatchProgress progress = new PatchProgress(logger); + if (!Directory.Exists(logsDirPath)) Directory.CreateDirectory(logsDirPath); + MessageQueue externalLogQueue = new MessageQueue(); + MessageQueue patchLogQueue = new MessageQueue(); + bool logThreadExitFlag = false; + ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate + { + QueueLogger externalLogger = new QueueLogger(externalLogQueue); + using (StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create), externalLogger)) + { + while (!logThreadExitFlag) + { + float waitTargetTime = Time.realtimeSinceStartup + TIME_TO_WAIT_FOR_LOGS; + + foreach (ILogMessage message in patchLogQueue.TakeAll()) + { + message.LogTo(streamLogger); + } + + foreach (ILogMessage message in externalLogQueue.TakeAll()) + { + message.LogTo(logger); + } + + float timeRemaining = waitTargetTime - Time.realtimeSinceStartup; + if (timeRemaining > 0) + System.Threading.Thread.Sleep((int)(timeRemaining * 1000)); + } + + foreach (ILogMessage message in patchLogQueue.TakeAll()) + { + message.LogTo(streamLogger); + } + + foreach (ILogMessage message in externalLogQueue.TakeAll()) + { + message.LogTo(logger); + } + + streamLogger.Info("Done!"); + } + }); + IBasicLogger patchLogger = new LogSplitter(logger, new QueueLogger(patchLogQueue)); + + IPatchProgress progress = new PatchProgress(patchLogger); status = "Pre patch init"; - logger.Info(status); - IEnumerable mods = ModListGenerator.GenerateModList(progress, logger); + patchLogger.Info(status); + IEnumerable mods = ModListGenerator.GenerateModList(progress, patchLogger); // If we don't use the cache then it is best to clean the PartDatabase.cfg if (!keepPartDB && File.Exists(partDatabasePath)) @@ -93,14 +140,14 @@ public IEnumerable Run() #region Sorting Patches status = "Extracting patches"; - logger.Info(status); + patchLogger.Info(status); UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); - INeedsChecker needsChecker = new NeedsChecker(mods, gameData, progress, logger); + INeedsChecker needsChecker = new NeedsChecker(mods, gameData, progress, patchLogger); ITagListParser tagListParser = new TagListParser(progress); IProtoPatchBuilder protoPatchBuilder = new ProtoPatchBuilder(progress); IPatchCompiler patchCompiler = new PatchCompiler(); - PatchExtractor extractor = new PatchExtractor(progress, logger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler); + PatchExtractor extractor = new PatchExtractor(progress, patchLogger, needsChecker, tagListParser, protoPatchBuilder, patchCompiler); // Have to convert to an array because we will be removing patches IEnumerable extractedPatches = @@ -112,7 +159,7 @@ public IEnumerable Run() #region Applying patches status = "Applying patches"; - logger.Info(status); + patchLogger.Info(status); IPass currentPass = null; float nextUpdate = Time.realtimeSinceStartup + yieldInterval; @@ -132,12 +179,12 @@ public IEnumerable Run() } }); - PatchApplier applier = new PatchApplier(progress, logger); + PatchApplier applier = new PatchApplier(progress, patchLogger); databaseConfigs = applier.ApplyPatches(patchList); StatusUpdate(progress); - logger.Info("Done patching"); + patchLogger.Info("Done patching"); #endregion Applying patches @@ -145,7 +192,7 @@ public IEnumerable Run() foreach (KeyValuePair item in progress.Counter.warningFiles) { - logger.Warning(item.Value + " warning" + (item.Value > 1 ? "s" : "") + " related to GameData/" + item.Key); + patchLogger.Warning(item.Value + " warning" + (item.Value > 1 ? "s" : "") + " related to GameData/" + item.Key); } if (progress.Counter.errors > 0 || progress.Counter.exceptions > 0) @@ -156,7 +203,7 @@ public IEnumerable Run() + "\n"; } - logger.Warning("Errors in patch prevents the creation of the cache"); + patchLogger.Warning("Errors in patch prevents the creation of the cache"); try { if (File.Exists(cachePath)) @@ -166,13 +213,13 @@ public IEnumerable Run() } catch (Exception e) { - logger.Exception("Exception while deleting stale cache ", e); + patchLogger.Exception("Exception while deleting stale cache ", e); } } else { status = "Saving Cache"; - logger.Info(status); + patchLogger.Info(status); CreateCache(databaseConfigs, progress.Counter.patchedNodes); } @@ -182,12 +229,35 @@ public IEnumerable Run() SaveModdedTechTree(); SaveModdedPhysics(); + + logThreadExitFlag = true; + + while (loggingThreadStatus.IsRunning) + { + System.Threading.Thread.Sleep(100); + } + + if (loggingThreadStatus.IsExitedWithError) + { + logger.Error("The patching thread threw an exception"); + throw loggingThreadStatus.Exception; + } } else { status = "Loading from Cache"; logger.Info(status); databaseConfigs = LoadCache(); + + if (File.Exists(patchLogPath)) + { + logger.Info("Dumping patch log"); + logger.Info("\n#### BEGIN PATCH LOG ####\n\n\n" + File.ReadAllText(patchLogPath) + "\n\n\n#### END PATCH LOG ####"); + } + else + { + logger.Error("Patch log does not exist: " + patchLogPath); + } } logger.Info(status + "\n" + errors); From ee9073b8a94c4ea4e8fbbf01c2e48704cb1088cb Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Mon, 24 Dec 2018 22:52:30 -0800 Subject: [PATCH 259/342] Get rid of stream logger's exception logger In theory it should be monitored, and not being able to log is a pretty serious condition we'd want to watch for --- ModuleManager/Logging/StreamLogger.cs | 51 +++++++------------ ModuleManager/MMPatchLoader.cs | 14 +---- ModuleManager/MMPatchRunner.cs | 14 +---- .../Logging/StreamLoggerTest.cs | 50 ++++-------------- 4 files changed, 29 insertions(+), 100 deletions(-) diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index 4da737e1..52ebd6bf 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -8,42 +8,34 @@ public class StreamLogger : IBasicLogger, IDisposable { private readonly Stream stream; private readonly StreamWriter streamWriter; - private readonly IBasicLogger exceptionLogger; private bool disposed = false; - public StreamLogger(Stream stream, IBasicLogger exceptionLogger) + public StreamLogger(Stream stream) { this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); if (!stream.CanWrite) throw new ArgumentException("must be writable", nameof(stream)); streamWriter = new StreamWriter(stream); - this.exceptionLogger = exceptionLogger ?? throw new ArgumentNullException(nameof(exceptionLogger)); } public void Log(LogType logType, string message) { if (disposed) throw new InvalidOperationException("Object has already been disposed"); - try - { - string prefix; - if (logType == LogType.Log) - prefix = "LOG"; - else if (logType == LogType.Warning) - prefix = "WRN"; - else if (logType == LogType.Error) - prefix = "ERR"; - else if (logType == LogType.Assert) - prefix = "AST"; - else if (logType == LogType.Exception) - prefix = "EXC"; - else - prefix = "UNK"; - streamWriter.WriteLine("[{0} {1}] {2}", prefix, DateTime.Now.ToString(), message); - } - catch (Exception e) - { - exceptionLogger.Exception("Exception while attempting to log to stream", e); - } + string prefix; + if (logType == LogType.Log) + prefix = "LOG"; + else if (logType == LogType.Warning) + prefix = "WRN"; + else if (logType == LogType.Error) + prefix = "ERR"; + else if (logType == LogType.Assert) + prefix = "AST"; + else if (logType == LogType.Exception) + prefix = "EXC"; + else + prefix = "UNK"; + + streamWriter.WriteLine("[{0} {1}] {2}", prefix, DateTime.Now.ToString(), message); } public void Exception(string message, Exception exception) @@ -53,15 +45,8 @@ public void Exception(string message, Exception exception) public void Dispose() { - try - { - // Flushes and closes the StreamWriter and the underlying stream - streamWriter.Close(); - } - catch(Exception e) - { - exceptionLogger.Exception("Exception while attempting to close stream writer", e); - } + // Flushes and closes the StreamWriter and the underlying stream + streamWriter.Close(); disposed = true; } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 49d6e326..f723f5b1 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -84,13 +84,11 @@ public IEnumerable Run() if (!useCache) { if (!Directory.Exists(logsDirPath)) Directory.CreateDirectory(logsDirPath); - MessageQueue externalLogQueue = new MessageQueue(); MessageQueue patchLogQueue = new MessageQueue(); bool logThreadExitFlag = false; ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { - QueueLogger externalLogger = new QueueLogger(externalLogQueue); - using (StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create), externalLogger)) + using (StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create))) { while (!logThreadExitFlag) { @@ -101,11 +99,6 @@ public IEnumerable Run() message.LogTo(streamLogger); } - foreach (ILogMessage message in externalLogQueue.TakeAll()) - { - message.LogTo(logger); - } - float timeRemaining = waitTargetTime - Time.realtimeSinceStartup; if (timeRemaining > 0) System.Threading.Thread.Sleep((int)(timeRemaining * 1000)); @@ -116,11 +109,6 @@ public IEnumerable Run() message.LogTo(streamLogger); } - foreach (ILogMessage message in externalLogQueue.TakeAll()) - { - message.LogTo(logger); - } - streamLogger.Info("Done!"); } }); diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 47b41371..01ab1aae 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -34,13 +34,11 @@ public IEnumerator Run() kspLogger.Info("Patching started on a new thread, all output will be directed to " + logPath); - MessageQueue kspLogQueue = new MessageQueue(); MessageQueue mmLogQueue = new MessageQueue(); bool logThreadExitFlag = false; ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { - QueueLogger kspLogger = new QueueLogger(kspLogQueue); - using (StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create), kspLogger)) + using (StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create))) { while (!logThreadExitFlag) { @@ -87,19 +85,9 @@ public IEnumerator Run() Status = patchLoader.status; Errors = patchLoader.errors; - foreach (ILogMessage message in kspLogQueue.TakeAll()) - { - message.LogTo(kspLogger); - } - if (!patchingThreadStatus.IsRunning && !loggingThreadStatus.IsRunning) break; } - foreach (ILogMessage message in kspLogQueue.TakeAll()) - { - message.LogTo(kspLogger); - } - if (patchingThreadStatus.IsExitedWithError) { kspLogger.Exception("The patching thread threw an exception", patchingThreadStatus.Exception); diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index 2b9f8733..fdcb832c 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -14,7 +14,7 @@ public void TestConstructor__StreamNull() { ArgumentNullException ex = Assert.Throws(delegate { - new StreamLogger(null, Substitute.For()); + new StreamLogger(null); }); Assert.Equal("stream", ex.ParamName); @@ -27,7 +27,7 @@ public void TestConstructor__CantWrite() { ArgumentException ex = Assert.Throws(delegate { - new StreamLogger(stream, Substitute.For()); + new StreamLogger(stream); }); Assert.Equal("stream", ex.ParamName); @@ -35,26 +35,12 @@ public void TestConstructor__CantWrite() } } - [Fact] - public void TestConstructor__ExceptionLoggerNull() - { - using (MemoryStream stream = new MemoryStream(new byte[0], true)) - { - ArgumentNullException ex = Assert.Throws(delegate - { - new StreamLogger(stream, null); - }); - - Assert.Equal("exceptionLogger", ex.ParamName); - } - } - [Fact] public void TestLog__AlreadyDisposed() { using (MemoryStream stream = new MemoryStream(new byte[0], true)) { - StreamLogger streamLogger = new StreamLogger(stream, Substitute.For()); + StreamLogger streamLogger = new StreamLogger(stream); streamLogger.Dispose(); InvalidOperationException ex = Assert.Throws(delegate @@ -69,18 +55,15 @@ public void TestLog__AlreadyDisposed() [Fact] public void TestLog__Log() { - IBasicLogger exceptionLogger = Substitute.For(); byte[] bytes = new byte[50]; using (MemoryStream stream = new MemoryStream(bytes, true)) { - using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + using (StreamLogger streamLogger = new StreamLogger(stream)) { streamLogger.Log(LogType.Log, "a message"); } } - exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); - using (MemoryStream stream = new MemoryStream(bytes, false)) { using (StreamReader reader = new StreamReader(stream)) @@ -95,18 +78,15 @@ public void TestLog__Log() [Fact] public void TestLog__Assert() { - IBasicLogger exceptionLogger = Substitute.For(); byte[] bytes = new byte[50]; using (MemoryStream stream = new MemoryStream(bytes, true)) { - using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + using (StreamLogger streamLogger = new StreamLogger(stream)) { streamLogger.Log(LogType.Assert, "a message"); } } - exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); - using (MemoryStream stream = new MemoryStream(bytes, false)) { using (StreamReader reader = new StreamReader(stream)) @@ -121,18 +101,15 @@ public void TestLog__Assert() [Fact] public void TestLog__Warning() { - IBasicLogger exceptionLogger = Substitute.For(); byte[] bytes = new byte[50]; using (MemoryStream stream = new MemoryStream(bytes, true)) { - using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + using (StreamLogger streamLogger = new StreamLogger(stream)) { streamLogger.Log(LogType.Warning, "a message"); } } - exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); - using (MemoryStream stream = new MemoryStream(bytes, false)) { using (StreamReader reader = new StreamReader(stream)) @@ -147,18 +124,15 @@ public void TestLog__Warning() [Fact] public void TestLog__Error() { - IBasicLogger exceptionLogger = Substitute.For(); byte[] bytes = new byte[50]; using (MemoryStream stream = new MemoryStream(bytes, true)) { - using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + using (StreamLogger streamLogger = new StreamLogger(stream)) { streamLogger.Log(LogType.Error, "a message"); } } - exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); - using (MemoryStream stream = new MemoryStream(bytes, false)) { using (StreamReader reader = new StreamReader(stream)) @@ -173,18 +147,15 @@ public void TestLog__Error() [Fact] public void TestLog__Exception() { - IBasicLogger exceptionLogger = Substitute.For(); byte[] bytes = new byte[50]; using (MemoryStream stream = new MemoryStream(bytes, true)) { - using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + using (StreamLogger streamLogger = new StreamLogger(stream)) { streamLogger.Log(LogType.Exception, "a message"); } } - exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); - using (MemoryStream stream = new MemoryStream(bytes, false)) { using (StreamReader reader = new StreamReader(stream)) @@ -199,18 +170,15 @@ public void TestLog__Exception() [Fact] public void TestLog__Unknown() { - IBasicLogger exceptionLogger = Substitute.For(); byte[] bytes = new byte[50]; using (MemoryStream stream = new MemoryStream(bytes, true)) { - using (StreamLogger streamLogger = new StreamLogger(stream, exceptionLogger)) + using (StreamLogger streamLogger = new StreamLogger(stream)) { streamLogger.Log((LogType)1000, "a message"); } } - exceptionLogger.DidNotReceiveWithAnyArgs().Exception(null, null); - using (MemoryStream stream = new MemoryStream(bytes, false)) { using (StreamReader reader = new StreamReader(stream)) From e0c1400300d9aafc649cce033ed99d85df50f24c Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Mon, 24 Dec 2018 22:53:15 -0800 Subject: [PATCH 260/342] Get rid of trailing whitespace Thanks VisualStudio --- ModuleManager/MMPatchLoader.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index f723f5b1..9685de23 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -306,11 +306,11 @@ private bool IsCacheUpToDate() // Hash the file path so the checksum change if files are moved byte[] pathBytes = Encoding.UTF8.GetBytes(files[i].url); sha.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); - + // hash the file content byte[] contentBytes = File.ReadAllBytes(files[i].fullPath); sha.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); - + filesha.ComputeHash(contentBytes); if (!filesSha.ContainsKey(files[i].url)) { @@ -372,7 +372,7 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) StringBuilder changes = new StringBuilder(); changes.Append("Changes :\n"); - + for (int i = 0; i < files.Length; i++) { ConfigNode fileNode = GetFileNode(shaConfigNode, files[i].url); @@ -418,7 +418,7 @@ private ConfigNode GetFileNode(ConfigNode shaConfigNode, string filename) } return null; } - + private void CreateCache(IEnumerable databaseConfigs, int patchedNodeCount) { @@ -512,7 +512,7 @@ private void SaveModdedTechTree() private IEnumerable LoadCache() { ConfigNode cache = ConfigNode.Load(cachePath); - + if (cache.HasValue("patchedNodeCount") && int.TryParse(cache.GetValue("patchedNodeCount"), out int patchedNodeCount)) status = "ModuleManager: " + patchedNodeCount + " patch" + (patchedNodeCount != 1 ? "es" : "") + " loaded from cache"; @@ -586,7 +586,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon #endif Command cmd = CommandParser.Parse(modVal.name, out string valName); - + Operator op; if (valName.Length > 2 && valName[valName.Length - 2] == ',') op = Operator.Assign; @@ -602,10 +602,10 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon context.progress.Error(context.patchUrl, "Error - Cannot find value assigning command: " + valName); continue; } - + if (op != Operator.Assign) { - if (double.TryParse(modVal.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s) + if (double.TryParse(modVal.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s) && double.TryParse(val.value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double os)) { switch (op) @@ -1000,7 +1000,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (n != null) subNodes.Add(n); } - + if (command == Command.Replace) { // if the original exists modify it @@ -1362,7 +1362,7 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod context.logger.Warning("Cannot find key " + valName + " in " + nodeStack.value.name); return null; } - + if (match.Groups[3].Success) { ConfigNode.Value newVal = new ConfigNode.Value(cVal.name, cVal.value); @@ -1471,7 +1471,7 @@ private static string FindAndReplaceValue( return null; } } - else if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s) + else if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double s) && double.TryParse(oValue, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out double os)) { switch (op) @@ -1543,7 +1543,7 @@ public static List SplitConstraints(string condition) public static bool CheckConstraints(ConfigNode node, string constraints) { constraints = constraints.RemoveWS(); - + if (constraints.Length == 0) return true; From 326119bc1ec3d1c2cf8b13f22f6d426e47b7e96a Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Mon, 24 Dec 2018 23:13:38 -0800 Subject: [PATCH 261/342] use international date format --- ModuleManager/Logging/StreamLogger.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index 52ebd6bf..c439149c 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -6,6 +6,8 @@ namespace ModuleManager.Logging { public class StreamLogger : IBasicLogger, IDisposable { + private const string DATETIME_FORMAT_STRING = "yyyy-mm-dd HH:mm:ss.fff"; + private readonly Stream stream; private readonly StreamWriter streamWriter; private bool disposed = false; @@ -35,7 +37,7 @@ public void Log(LogType logType, string message) else prefix = "UNK"; - streamWriter.WriteLine("[{0} {1}] {2}", prefix, DateTime.Now.ToString(), message); + streamWriter.WriteLine("[{0} {1}] {2}", prefix, DateTime.Now.ToString(DATETIME_FORMAT_STRING), message); } public void Exception(string message, Exception exception) From 072b0d002a2e3facd4850a6a9ec9efdd7f6c99c5 Mon Sep 17 00:00:00 2001 From: Joseph Wong Date: Thu, 27 Dec 2018 18:18:45 -0800 Subject: [PATCH 262/342] extract common parts of reading logs from queue to its own class --- ModuleManager/Collections/MessageQueue.cs | 7 +- ModuleManager/Logging/QueueLogRunner.cs | 68 ++++++++ ModuleManager/MMPatchLoader.cs | 24 +-- ModuleManager/MMPatchRunner.cs | 24 +-- ModuleManager/ModuleManager.csproj | 1 + .../Collections/MessageQueueTest.cs | 2 +- .../Logging/QueueLogRunnerTest.cs | 152 ++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 8 files changed, 233 insertions(+), 46 deletions(-) create mode 100644 ModuleManager/Logging/QueueLogRunner.cs create mode 100644 ModuleManagerTests/Logging/QueueLogRunnerTest.cs diff --git a/ModuleManager/Collections/MessageQueue.cs b/ModuleManager/Collections/MessageQueue.cs index 9f1bc864..321a3503 100644 --- a/ModuleManager/Collections/MessageQueue.cs +++ b/ModuleManager/Collections/MessageQueue.cs @@ -4,9 +4,10 @@ namespace ModuleManager.Collections { - public interface IMessageQueue + public interface IMessageQueue : IEnumerable { void Add(T value); + IMessageQueue TakeAll(); } public class MessageQueue : IMessageQueue, IEnumerable @@ -56,7 +57,7 @@ public Node(T value) private readonly object lockObject = new object(); private Node head; private Node tail; - + public void Add(T value) { Node node = new Node(value); @@ -75,7 +76,7 @@ public void Add(T value) } } - public MessageQueue TakeAll() + public IMessageQueue TakeAll() { MessageQueue queue = new MessageQueue(); lock(lockObject) diff --git a/ModuleManager/Logging/QueueLogRunner.cs b/ModuleManager/Logging/QueueLogRunner.cs new file mode 100644 index 00000000..2ed5c300 --- /dev/null +++ b/ModuleManager/Logging/QueueLogRunner.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; + +using ModuleManager.Collections; + +namespace ModuleManager.Logging +{ + public class QueueLogRunner + { + private enum State + { + Initialized, + Running, + StopRequested, + Stopped, + } + + private State state = State.Initialized; + private readonly IMessageQueue logQueue; + private readonly long timeToWaitForLogsMs; + + public QueueLogRunner(IMessageQueue logQueue, long timeToWaitForLogsMs = 50) + { + this.logQueue = logQueue ?? throw new ArgumentNullException(nameof(logQueue)); + if (timeToWaitForLogsMs < 0) throw new ArgumentException("must be non-negative", nameof(timeToWaitForLogsMs)); + this.timeToWaitForLogsMs = timeToWaitForLogsMs; + } + + public void RequestStop() + { + if (state == State.StopRequested || state == State.Stopped) return; + if (state != State.Running) throw new InvalidOperationException($"Cannot request stop from {state} state"); + state = State.StopRequested; + } + + public void Run(IBasicLogger logger) + { + if (state != State.Initialized) throw new InvalidOperationException($"Cannot run from {state} state"); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + state = State.Running; + + System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); + + while (state == State.Running) + { + stopwatch.Start(); + + foreach (ILogMessage message in logQueue.TakeAll()) + { + message.LogTo(logger); + } + + long timeRemaining = timeToWaitForLogsMs - stopwatch.ElapsedMilliseconds; + if (timeRemaining > 0) + System.Threading.Thread.Sleep((int)timeRemaining); + + stopwatch.Reset(); + } + + foreach (ILogMessage message in logQueue.TakeAll()) + { + message.LogTo(logger); + } + + state = State.Stopped; + } + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 9685de23..0e94bca8 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -85,30 +85,12 @@ public IEnumerable Run() { if (!Directory.Exists(logsDirPath)) Directory.CreateDirectory(logsDirPath); MessageQueue patchLogQueue = new MessageQueue(); - bool logThreadExitFlag = false; + QueueLogRunner logRunner = new QueueLogRunner(patchLogQueue); ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { using (StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create))) { - while (!logThreadExitFlag) - { - float waitTargetTime = Time.realtimeSinceStartup + TIME_TO_WAIT_FOR_LOGS; - - foreach (ILogMessage message in patchLogQueue.TakeAll()) - { - message.LogTo(streamLogger); - } - - float timeRemaining = waitTargetTime - Time.realtimeSinceStartup; - if (timeRemaining > 0) - System.Threading.Thread.Sleep((int)(timeRemaining * 1000)); - } - - foreach (ILogMessage message in patchLogQueue.TakeAll()) - { - message.LogTo(streamLogger); - } - + logRunner.Run(streamLogger); streamLogger.Info("Done!"); } }); @@ -218,7 +200,7 @@ public IEnumerable Run() SaveModdedTechTree(); SaveModdedPhysics(); - logThreadExitFlag = true; + logRunner.RequestStop(); while (loggingThreadStatus.IsRunning) { diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 01ab1aae..6852464c 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -35,30 +35,12 @@ public IEnumerator Run() kspLogger.Info("Patching started on a new thread, all output will be directed to " + logPath); MessageQueue mmLogQueue = new MessageQueue(); - bool logThreadExitFlag = false; + QueueLogRunner logRunner = new QueueLogRunner(mmLogQueue); ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { using (StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create))) { - while (!logThreadExitFlag) - { - float waitTargetTime = Time.realtimeSinceStartup + TIME_TO_WAIT_FOR_LOGS; - - foreach (ILogMessage message in mmLogQueue.TakeAll()) - { - message.LogTo(streamLogger); - } - - float timeRemaining = waitTargetTime - Time.realtimeSinceStartup; - if (timeRemaining > 0) - System.Threading.Thread.Sleep((int)(timeRemaining * 1000)); - } - - foreach (ILogMessage message in mmLogQueue.TakeAll()) - { - message.LogTo(streamLogger); - } - + logRunner.Run(streamLogger); streamLogger.Info("Done!"); } }); @@ -80,7 +62,7 @@ public IEnumerator Run() yield return null; if (!patchingThreadStatus.IsRunning) - logThreadExitFlag = true; + logRunner.RequestStop(); Status = patchLoader.status; Errors = patchLoader.errors; diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 51550b90..92ce283a 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -54,6 +54,7 @@ + diff --git a/ModuleManagerTests/Collections/MessageQueueTest.cs b/ModuleManagerTests/Collections/MessageQueueTest.cs index 9ee116ff..83bf1217 100644 --- a/ModuleManagerTests/Collections/MessageQueueTest.cs +++ b/ModuleManagerTests/Collections/MessageQueueTest.cs @@ -42,7 +42,7 @@ public void TestTakeAll() queue.Add(o2); queue.Add(o3); - MessageQueue queue2 = queue.TakeAll(); + MessageQueue queue2 = Assert.IsType>(queue.TakeAll()); queue.Add(o4); diff --git a/ModuleManagerTests/Logging/QueueLogRunnerTest.cs b/ModuleManagerTests/Logging/QueueLogRunnerTest.cs new file mode 100644 index 00000000..a52e7add --- /dev/null +++ b/ModuleManagerTests/Logging/QueueLogRunnerTest.cs @@ -0,0 +1,152 @@ +using System; +using Xunit; +using NSubstitute; + +using ModuleManager.Collections; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class QueueLogRunnerTest + { + [Fact] + public void TestConstructor__LogQueueNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new QueueLogRunner(null); + }); + + Assert.Equal("logQueue", ex.ParamName); + } + + [Fact] + public void TestConstructor__TimeToWaitForLogsMsNegative() + { + ArgumentException ex = Assert.Throws(delegate + { + new QueueLogRunner(Substitute.For>(), -1); + }); + + Assert.Contains("must be non-negative", ex.Message); + Assert.Equal("timeToWaitForLogsMs", ex.ParamName); + } + + [Fact] + public void TestRun() + { + ILogMessage message1 = Substitute.For(); + ILogMessage message2 = Substitute.For(); + ILogMessage message3 = Substitute.For(); + ILogMessage message4 = Substitute.For(); + ILogMessage message5 = Substitute.For(); + ILogMessage message6 = Substitute.For(); + IMessageQueue messageQueue = Substitute.For>(); + QueueLogRunner logRunner = new QueueLogRunner(messageQueue, 0); + int counter = 0; + messageQueue.TakeAll().Returns(delegate + { + IMessageQueue messageQueue2 = Substitute.For>(); + if (counter == 0) + { + messageQueue2.GetEnumerator().Returns(new ArrayEnumerator(message1, message2)); + } + else if (counter == 1) + { + logRunner.RequestStop(); // Called from Running state + messageQueue2.GetEnumerator().Returns(new ArrayEnumerator(message3, message4)); + } + else + { + logRunner.RequestStop(); // Called from StopRequested state + messageQueue2.GetEnumerator().Returns(new ArrayEnumerator(message5, message6)); + } + counter++; + return messageQueue2; + }); + + IBasicLogger logger = Substitute.For(); + + logRunner.Run(logger); + + logRunner.RequestStop(); // Called from Stopped state + + Received.InOrder(delegate + { + message1.LogTo(logger); + message2.LogTo(logger); + message3.LogTo(logger); + message4.LogTo(logger); + message5.LogTo(logger); + message6.LogTo(logger); + }); + } + + [Fact] + public void TestRun__AlreadyStarted() + { + IMessageQueue messageQueue = Substitute.For>(); + QueueLogRunner logRunner = new QueueLogRunner(messageQueue, 0); + int counter = 0; + messageQueue.TakeAll().Returns(delegate + { + IMessageQueue messageQueue2 = Substitute.For>(); + if (counter == 0) + { + InvalidOperationException ex = Assert.Throws(delegate + { + logRunner.Run(Substitute.For()); + }); + Assert.Equal("Cannot run from Running state", ex.Message); + logRunner.RequestStop(); + messageQueue2.GetEnumerator().Returns(new ArrayEnumerator()); + } + else + { + InvalidOperationException ex = Assert.Throws(delegate + { + logRunner.Run(Substitute.For()); + }); + Assert.Equal("Cannot run from StopRequested state", ex.Message); + logRunner.RequestStop(); + messageQueue2.GetEnumerator().Returns(new ArrayEnumerator()); + } + counter++; + return messageQueue2; + }); + + IBasicLogger logger = Substitute.For(); + logRunner.Run(logger); + + InvalidOperationException ex2 = Assert.Throws(delegate + { + logRunner.Run(Substitute.For()); + }); + Assert.Equal("Cannot run from Stopped state", ex2.Message); + } + + [Fact] + public void TestRun__LoggerNull() + { + QueueLogRunner logRunner = new QueueLogRunner(Substitute.For>()); + + ArgumentNullException ex = Assert.Throws(delegate + { + logRunner.Run(null); + }); + Assert.Equal("logger", ex.ParamName); + } + + [Fact] + public void TestRequestStop__NotStarted() + { + QueueLogRunner logRunner = new QueueLogRunner(Substitute.For>()); + + InvalidOperationException ex = Assert.Throws(delegate + { + logRunner.RequestStop(); + }); + Assert.Equal("Cannot request stop from Initialized state", ex.Message); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index f5d9a47e..7e1bd948 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -88,6 +88,7 @@ + From 166b72c9a2a087c3b58e4e3e4ea949171c48c9c7 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Thu, 24 Jan 2019 20:15:25 +0100 Subject: [PATCH 263/342] v4.0.0 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index d3094e3a..56e080a3 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("3.1.3")] +[assembly: AssemblyVersion("4.0.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 05b2e9aa869bad0acb15880c8ba2729f7b2a076f Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 24 Jan 2019 23:03:56 -0800 Subject: [PATCH 264/342] Delete unnecessary space --- ModuleManager/CustomConfigsManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/CustomConfigsManager.cs b/ModuleManager/CustomConfigsManager.cs index b749ba2f..e2b8689c 100644 --- a/ModuleManager/CustomConfigsManager.cs +++ b/ModuleManager/CustomConfigsManager.cs @@ -11,7 +11,7 @@ public class CustomConfigsManager : MonoBehaviour { internal void Start() { - if (HighLogic.CurrentGame.Parameters.Career.TechTreeUrl != techTreeFile && File.Exists(techTreePath)) + if (HighLogic.CurrentGame.Parameters.Career.TechTreeUrl != techTreeFile && File.Exists(techTreePath)) { Log("Setting modded tech tree as the active one"); HighLogic.CurrentGame.Parameters.Career.TechTreeUrl = techTreeFile; From 22a05651acde051c1d601d5dae4ef224d4f1d8a2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 24 Jan 2019 23:46:38 -0800 Subject: [PATCH 265/342] Fix modded physics and tech tree * Both should be saved from proto configs, not real game database since at that point patched nodes are not copied to real database * Don't need to keep track of physics url file --- ModuleManager/MMPatchLoader.cs | 37 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 0e94bca8..fc1dc42c 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -36,8 +36,6 @@ public class MMPatchLoader private static readonly Dictionary regexCache = new Dictionary(); - private UrlDir.UrlFile physicsUrlFile; - private string configSha; private Dictionary filesSha = new Dictionary(); @@ -197,8 +195,8 @@ public IEnumerable Run() #endregion Saving Cache - SaveModdedTechTree(); - SaveModdedPhysics(); + SaveModdedTechTree(databaseConfigs); + SaveModdedPhysics(databaseConfigs); logRunner.RequestStop(); @@ -243,7 +241,7 @@ private void LoadPhysicsConfig() logger.Info("Loading Physics.cfg"); UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); // need to use a file with a cfg extension to get the right fileType or you can't AddConfig on it - physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); + UrlDir.UrlFile physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); // Since it loaded the default config badly (sub node only) we clear it first physicsUrlFile.configs.Clear(); // And reload it properly @@ -253,23 +251,23 @@ private void LoadPhysicsConfig() gameDataDir.files.Add(physicsUrlFile); } - - private void SaveModdedPhysics() + private void SaveModdedPhysics(IEnumerable databaseConfigs) { - List configs = physicsUrlFile.configs; + IEnumerable configs = databaseConfigs.Where(config => config.NodeType == "PHYSICSGLOBALS"); + int count = configs.Count(); - if (configs.Count == 0) + if (count == 0) { logger.Info("No PHYSICSGLOBALS node found. No custom Physics config will be saved"); return; } - if (configs.Count > 1) + if (count > 1) { - logger.Info(configs.Count + " PHYSICSGLOBALS node found. A patch may be wrong. Using the first one"); + logger.Info($"{count} PHYSICSGLOBALS node found. A patch may be wrong. Using the first one"); } - configs[0].config.Save(physicsPath); + configs.First().Node.Save(physicsPath); } private bool IsCacheUpToDate() @@ -471,23 +469,24 @@ private void CreateCache(IEnumerable databaseConfigs, int patch } } - private void SaveModdedTechTree() + private void SaveModdedTechTree(IEnumerable databaseConfigs) { - UrlDir.UrlConfig[] configs = GameDatabase.Instance.GetConfigs("TechTree"); + IEnumerable configs = databaseConfigs.Where(config => config.NodeType == "TechTree"); + int count = configs.Count(); - if (configs.Length == 0) + if (count == 0) { logger.Info("No TechTree node found. No custom TechTree will be saved"); return; } - if (configs.Length > 1) + if (count > 1) { - logger.Info(configs.Length + " TechTree node found. A patch may be wrong. Using the first one"); + logger.Info($"{count} TechTree node found. A patch may be wrong. Using the first one"); } ConfigNode techNode = new ConfigNode("TechTree"); - techNode.AddNode(configs[0].config); + techNode.AddNode(configs.First().Node); techNode.Save(techTreePath); } @@ -501,7 +500,7 @@ private IEnumerable LoadCache() // Create the fake file where we load the physic config cache UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); // need to use a file with a cfg extension to get the right fileType or you can't AddConfig on it - physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); + UrlDir.UrlFile physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); gameDataDir.files.Add(physicsUrlFile); List databaseConfigs = new List(cache.nodes.Count); From 65f0a36beb784c910726287981870b3a5579bc2d Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 24 Jan 2019 23:50:24 -0800 Subject: [PATCH 266/342] Make physics and tech tree node names constant --- ModuleManager/MMPatchLoader.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index fc1dc42c..4130cf4e 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -28,6 +28,9 @@ namespace ModuleManager [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public class MMPatchLoader { + private const string PHYSICS_NODE_NAME = "PHYSICSGLOBALS"; + private const string TECH_TREE_NODE_NAME = "TechTree"; + public string status = ""; public string errors = ""; @@ -246,25 +249,25 @@ private void LoadPhysicsConfig() physicsUrlFile.configs.Clear(); // And reload it properly ConfigNode physicsContent = ConfigNode.Load(defaultPhysicsPath); - physicsContent.name = "PHYSICSGLOBALS"; + physicsContent.name = PHYSICS_NODE_NAME; physicsUrlFile.AddConfig(physicsContent); gameDataDir.files.Add(physicsUrlFile); } private void SaveModdedPhysics(IEnumerable databaseConfigs) { - IEnumerable configs = databaseConfigs.Where(config => config.NodeType == "PHYSICSGLOBALS"); + IEnumerable configs = databaseConfigs.Where(config => config.NodeType == PHYSICS_NODE_NAME); int count = configs.Count(); if (count == 0) { - logger.Info("No PHYSICSGLOBALS node found. No custom Physics config will be saved"); + logger.Info($"No {PHYSICS_NODE_NAME} node found. No custom Physics config will be saved"); return; } if (count > 1) { - logger.Info($"{count} PHYSICSGLOBALS node found. A patch may be wrong. Using the first one"); + logger.Info($"{count} {PHYSICS_NODE_NAME} nodes found. A patch may be wrong. Using the first one"); } configs.First().Node.Save(physicsPath); @@ -471,21 +474,21 @@ private void CreateCache(IEnumerable databaseConfigs, int patch private void SaveModdedTechTree(IEnumerable databaseConfigs) { - IEnumerable configs = databaseConfigs.Where(config => config.NodeType == "TechTree"); + IEnumerable configs = databaseConfigs.Where(config => config.NodeType == TECH_TREE_NODE_NAME); int count = configs.Count(); if (count == 0) { - logger.Info("No TechTree node found. No custom TechTree will be saved"); + logger.Info($"No {TECH_TREE_NODE_NAME} node found. No custom {TECH_TREE_NODE_NAME} will be saved"); return; } if (count > 1) { - logger.Info($"{count} TechTree node found. A patch may be wrong. Using the first one"); + logger.Info($"{count} {TECH_TREE_NODE_NAME} nodes found. A patch may be wrong. Using the first one"); } - ConfigNode techNode = new ConfigNode("TechTree"); + ConfigNode techNode = new ConfigNode(TECH_TREE_NODE_NAME); techNode.AddNode(configs.First().Node); techNode.Save(techTreePath); } From 89a807960ba09dee297cdecdd51d231640e7215a Mon Sep 17 00:00:00 2001 From: sarbian Date: Fri, 25 Jan 2019 10:36:36 +0100 Subject: [PATCH 267/342] v4.0.1 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 56e080a3..5ad144f3 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.0.0")] +[assembly: AssemblyVersion("4.0.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 4ebd40a5b6ebd770c0cd0ea5d3c280fbc22b706b Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Feb 2019 00:27:26 -0800 Subject: [PATCH 268/342] Fix datetime format Don't want minute as month --- ModuleManager/Logging/StreamLogger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index c439149c..8f4b131a 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -6,7 +6,7 @@ namespace ModuleManager.Logging { public class StreamLogger : IBasicLogger, IDisposable { - private const string DATETIME_FORMAT_STRING = "yyyy-mm-dd HH:mm:ss.fff"; + private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; private readonly Stream stream; private readonly StreamWriter streamWriter; From 2b356b0db6578ce2c723acbb566a5756ae75775b Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 1 Feb 2019 21:17:58 -0800 Subject: [PATCH 269/342] Use KSP's directory listing rather than file system Avoids asking for Unity's application root path on a thread, which apparently breaks badly in debug mode --- ModuleManager/ModListGenerator.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ModuleManager/ModListGenerator.cs b/ModuleManager/ModListGenerator.cs index cca98805..bd0e8453 100644 --- a/ModuleManager/ModListGenerator.cs +++ b/ModuleManager/ModListGenerator.cs @@ -117,11 +117,10 @@ public static IEnumerable GenerateModList(IPatchProgress progress, IBasi } } modListInfo.Append("Mods by directory (sub directories of GameData):\n"); - string gameData = Path.Combine(Path.GetFullPath(KSPUtil.ApplicationRootPath), "GameData"); - foreach (string subdir in Directory.GetDirectories(gameData)) + UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData); + foreach (UrlDir subDir in gameData.children) { - string name = Path.GetFileName(subdir); - string cleanName = name.RemoveWS(); + string cleanName = subDir.name.RemoveWS(); if (!mods.Contains(cleanName, StringComparer.OrdinalIgnoreCase)) { mods.Add(cleanName); From 9d9808790ce4a9ab345a69c9ba0ccfe2a992c865 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Feb 2019 00:43:22 -0800 Subject: [PATCH 270/342] Better status updates in post patch Each step can take a while, so explicitly say what's going on --- ModuleManager/PostPatchLoader.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index 630b3818..9da8ce4b 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -26,6 +26,8 @@ public class PostPatchLoader : LoadingSystem private bool ready = false; + private string progressTitle = "ModuleManager: Post patch"; + public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) { if (!postPatchCallbacks.Contains(callback)) @@ -48,7 +50,7 @@ private void Awake() public override float ProgressFraction() => 1; - public override string ProgressTitle() => "ModuleManager : post patch"; + public override string ProgressTitle() => progressTitle; public override void StartLoad() { @@ -61,6 +63,8 @@ private IEnumerator Run() Stopwatch waitTimer = new Stopwatch(); waitTimer.Start(); + progressTitle = "ModuleManager: Waiting for patching to finish"; + while (databaseConfigs == null) yield return null; waitTimer.Stop(); @@ -69,6 +73,7 @@ private IEnumerator Run() Stopwatch postPatchTimer = new Stopwatch(); postPatchTimer.Start(); + progressTitle = "ModuleManager: Applying patched game database"; logger.Info("Applying patched game database"); foreach (UrlDir.UrlFile file in GameDatabase.Instance.root.AllConfigFiles) @@ -87,6 +92,7 @@ private IEnumerator Run() if (File.Exists(logPath)) { + progressTitle = "ModuleManager: Dumping log to KSP log"; logger.Info("Dumping ModuleManager log to main log"); logger.Info("\n#### BEGIN MODULEMANAGER LOG ####\n\n\n" + File.ReadAllText(logPath) + "\n\n\n#### END MODULEMANAGER LOG ####"); } @@ -104,6 +110,8 @@ private IEnumerator Run() yield return null; + progressTitle = "ModuleManager: Reloading things"; + logger.Info("Reloading resources definitions"); PartResourceLibrary.Instance.LoadDefinitions(); @@ -115,6 +123,7 @@ private IEnumerator Run() yield return null; + progressTitle = "ModuleManager: Running post patch callbacks"; logger.Info("Running post patch callbacks"); foreach (ModuleManagerPostPatchCallback callback in postPatchCallbacks) From 20d339ad0922fe38d70acd517452909d7c27ab61 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Feb 2019 00:54:43 -0800 Subject: [PATCH 271/342] Add -mm-dont-copy-logs command line flag If set, don't copy MM log to main log. Intended for advanced users with very larger installs where this takes a significant amount of time --- ModuleManager/ModuleManager.cs | 3 +++ ModuleManager/PostPatchLoader.cs | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index ac92cddc..4d7e76de 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -33,6 +33,7 @@ public class ModuleManager : MonoBehaviour private bool nyan = false; private bool nCats = false; public static bool dumpPostPatch = false; + public static bool DontCopyLogs { get; private set; } = false; private PopupDialog menu; @@ -152,6 +153,8 @@ internal void Awake() dumpPostPatch = Environment.GetCommandLineArgs().Contains("-mm-dump"); + DontCopyLogs = Environment.GetCommandLineArgs().Contains("-mm-dont-copy-logs"); + loadedInScene = true; } diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index 9da8ce4b..758ab0ae 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -92,9 +92,16 @@ private IEnumerator Run() if (File.Exists(logPath)) { - progressTitle = "ModuleManager: Dumping log to KSP log"; - logger.Info("Dumping ModuleManager log to main log"); - logger.Info("\n#### BEGIN MODULEMANAGER LOG ####\n\n\n" + File.ReadAllText(logPath) + "\n\n\n#### END MODULEMANAGER LOG ####"); + if (ModuleManager.DontCopyLogs) + { + logger.Info("Not dumping log because -mm-dont-copy-logs was set"); + } + else + { + progressTitle = "ModuleManager: Dumping log to KSP log"; + logger.Info("Dumping ModuleManager log to main log"); + logger.Info("\n#### BEGIN MODULEMANAGER LOG ####\n\n\n" + File.ReadAllText(logPath) + "\n\n\n#### END MODULEMANAGER LOG ####"); + } } else { From c43268f5d4ece8ef2f5c5c55537cd12db081c6cf Mon Sep 17 00:00:00 2001 From: blowfish Date: Sat, 2 Feb 2019 00:19:23 -0800 Subject: [PATCH 272/342] Allow assemblies to add to mod list Allow ModuleManagerAddToModList callback to be defined as a static method or on MonoBehaviour instances which returns an IEnumerable to be added to the mod list It will always be called on the main thread Result will also be added to the config sha to ensure proper cache invalidation --- ModuleManager/MMPatchLoader.cs | 14 +++- ModuleManager/MMPatchRunner.cs | 6 +- ModuleManager/ModListGenerator.cs | 107 +++++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 5 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 4130cf4e..93447f74 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -46,15 +46,17 @@ public class MMPatchLoader private const float TIME_TO_WAIT_FOR_LOGS = 0.05f; - private IBasicLogger logger; + private readonly IEnumerable modsAddedByAssemblies; + private readonly IBasicLogger logger; public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) { PostPatchLoader.AddPostPatchCallback(callback); } - public MMPatchLoader(IBasicLogger logger) + public MMPatchLoader(IEnumerable modsAddedByAssemblies, IBasicLogger logger) { + this.modsAddedByAssemblies = modsAddedByAssemblies ?? throw new ArgumentNullException(nameof(modsAddedByAssemblies)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -100,7 +102,7 @@ public IEnumerable Run() IPatchProgress progress = new PatchProgress(patchLogger); status = "Pre patch init"; patchLogger.Info(status); - IEnumerable mods = ModListGenerator.GenerateModList(progress, patchLogger); + IEnumerable mods = ModListGenerator.GenerateModList(modsAddedByAssemblies, progress, patchLogger); // If we don't use the cache then it is best to clean the PartDatabase.cfg if (!keepPartDB && File.Exists(partDatabasePath)) @@ -313,6 +315,12 @@ private bool IsCacheUpToDate() sha.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); } + foreach (ModListGenerator.ModAddedByAssembly mod in modsAddedByAssemblies) + { + byte[] modBytes = Encoding.UTF8.GetBytes(mod.modName); + sha.TransformBlock(modBytes, 0, modBytes.Length, modBytes, 0); + } + byte[] godsFinalMessageToHisCreation = Encoding.UTF8.GetBytes("We apologize for the inconvenience."); sha.TransformFinalBlock(godsFinalMessageToHisCreation, 0, godsFinalMessageToHisCreation.Length); diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 6852464c..e6138254 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -48,9 +48,13 @@ public IEnumerator Run() // Wait for game database to be initialized for the 2nd time yield return null; + IBasicLogger mmLogger = new QueueLogger(mmLogQueue); + + IEnumerable modsAddedByAssemblies = ModListGenerator.GetAdditionalModsFromStaticMethods(mmLogger); + IEnumerable databaseConfigs = null; - MMPatchLoader patchLoader = new MMPatchLoader(new QueueLogger(mmLogQueue)); + MMPatchLoader patchLoader = new MMPatchLoader(modsAddedByAssemblies, mmLogger); ITaskStatus patchingThreadStatus = BackgroundTask.Start(delegate { diff --git a/ModuleManager/ModListGenerator.cs b/ModuleManager/ModListGenerator.cs index bd0e8453..97292ba0 100644 --- a/ModuleManager/ModListGenerator.cs +++ b/ModuleManager/ModListGenerator.cs @@ -5,6 +5,7 @@ using System.Text; using System.Diagnostics; using System.Reflection; +using UnityEngine; using ModuleManager.Extensions; using ModuleManager.Logging; using ModuleManager.Utils; @@ -14,7 +15,7 @@ namespace ModuleManager { public static class ModListGenerator { - public static IEnumerable GenerateModList(IPatchProgress progress, IBasicLogger logger) + public static IEnumerable GenerateModList(IEnumerable modsAddedByAssemblies, IPatchProgress progress, IBasicLogger logger) { #region List of mods @@ -127,6 +128,17 @@ public static IEnumerable GenerateModList(IPatchProgress progress, IBasi modListInfo.AppendFormat(" {0}\n", cleanName); } } + + modListInfo.Append("Mods added by assemblies:\n"); + foreach (ModAddedByAssembly mod in modsAddedByAssemblies) + { + if (!mods.Contains(mod.modName, StringComparer.OrdinalIgnoreCase)) + { + mods.Add(mod.modName); + modListInfo.AppendFormat(" {0}\n", mod); + } + } + logger.Info(modListInfo.ToString()); mods.Sort(); @@ -135,5 +147,98 @@ public static IEnumerable GenerateModList(IPatchProgress progress, IBasi return mods; } + + public class ModAddedByAssembly + { + public readonly string modName; + public readonly string assemblyName; + + public ModAddedByAssembly(string modName, string assemblyName) + { + this.modName = modName ?? throw new ArgumentNullException(nameof(modName)); + this.assemblyName = assemblyName ?? throw new ArgumentNullException(nameof(assemblyName)); + } + + public override string ToString() + { + return $"{modName} (added by {assemblyName})"; + } + } + + public static IEnumerable GetAdditionalModsFromStaticMethods(IBasicLogger logger) + { + List result = new List(); + foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies()) + { + try + { + foreach (Type type in ass.GetTypes()) + { + MethodInfo method = type.GetMethod("ModuleManagerAddToModList", BindingFlags.Public | BindingFlags.Static); + + if (method != null && method.GetParameters().Length == 0 && typeof(IEnumerable).IsAssignableFrom(method.ReturnType)) + { + string methodName = $"{ass.GetName().Name}.{type.Name}.{method.Name}()"; + try + { + logger.Info("Calling " + methodName); + IEnumerable modsToAdd = (IEnumerable)method.Invoke(null, null); + + if (modsToAdd == null) + { + logger.Error("ModuleManagerAddToModList returned null: " + methodName); + continue; + } + + foreach (string mod in modsToAdd) + { + result.Add(new ModAddedByAssembly(mod, ass.GetName().Name)); + } + } + catch (Exception e) + { + logger.Exception("Exception while calling " + methodName, e); + } + } + } + } + catch (Exception e) + { + logger.Exception("Add to mod list threw an exception in loading " + ass.FullName, e); + } + } + + foreach (MonoBehaviour obj in UnityEngine.Object.FindObjectsOfType()) + { + MethodInfo method = obj.GetType().GetMethod("ModuleManagerAddToModList", BindingFlags.Public | BindingFlags.Instance); + + if (method != null && method.GetParameters().Length == 0 && typeof(IEnumerable).IsAssignableFrom(method.ReturnType)) + { + string methodName = $"{obj.GetType().Name}.{method.Name}()"; + try + { + logger.Info("Calling " + methodName); + IEnumerable modsToAdd = (IEnumerable)method.Invoke(obj, null); + + if (modsToAdd == null) + { + logger.Error("ModuleManagerAddToModList returned null: " + methodName); + continue; + } + + foreach (string mod in modsToAdd) + { + result.Add(new ModAddedByAssembly(mod, obj.GetType().Assembly.GetName().Name)); + } + } + catch (Exception e) + { + logger.Exception("Exception while calling " + methodName, e); + } + } + } + + return result; + } } } From 811fe8afa127b2bbad03ff5d1189a0c0c54f273b Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Sat, 2 Feb 2019 15:44:25 -0800 Subject: [PATCH 273/342] wait another frame for plugins to finish initializing --- ModuleManager/MMPatchRunner.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index e6138254..802b76fb 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -45,7 +45,8 @@ public IEnumerator Run() } }); - // Wait for game database to be initialized for the 2nd time + // Wait for game database to be initialized for the 2nd time and wait for any plugins to initialize + yield return null; yield return null; IBasicLogger mmLogger = new QueueLogger(mmLogQueue); From 922114970bbbff001d70cdd3a833419407faaa38 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Sat, 2 Feb 2019 21:33:40 -0800 Subject: [PATCH 274/342] Don't use Unity's time since startup in thread We only care about delta time here anyway so stopwatch should be fine --- ModuleManager/MMPatchLoader.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 93447f74..f3e0ab91 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -42,9 +42,7 @@ public class MMPatchLoader private string configSha; private Dictionary filesSha = new Dictionary(); - private const float yieldInterval = 1f/30f; // Patch at ~30fps - - private const float TIME_TO_WAIT_FOR_LOGS = 0.05f; + private const int STATUS_UPDATE_INVERVAL_MS = 33; private readonly IEnumerable modsAddedByAssemblies; private readonly IBasicLogger logger; @@ -135,7 +133,6 @@ public IEnumerable Run() patchLogger.Info(status); IPass currentPass = null; - float nextUpdate = Time.realtimeSinceStartup + yieldInterval; progress.OnPassStarted.Add(delegate (IPass pass) { @@ -143,18 +140,24 @@ public IEnumerable Run() StatusUpdate(progress, currentPass.Name); }); + System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); + stopwatch.Start(); + progress.OnPatchApplied.Add(delegate { - if (Time.realtimeSinceStartup > nextUpdate) + long timeRemaining = STATUS_UPDATE_INVERVAL_MS - stopwatch.ElapsedMilliseconds; + if (timeRemaining < 0) { StatusUpdate(progress, currentPass.Name); - nextUpdate = Time.realtimeSinceStartup + yieldInterval; + stopwatch.Reset(); + stopwatch.Start(); } }); PatchApplier applier = new PatchApplier(progress, patchLogger); databaseConfigs = applier.ApplyPatches(patchList); + stopwatch.Stop(); StatusUpdate(progress); patchLogger.Info("Done patching"); From 6a2ee3ddad4a8c6088886968f45dbf2da8a5c6eb Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sun, 3 Feb 2019 19:39:39 +0100 Subject: [PATCH 275/342] v4.0.2 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 5ad144f3..43b2dc37 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.0.1")] +[assembly: AssemblyVersion("4.0.2")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 918b0a24de7f604b31a058395e4e2709c7dc8ede Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 19 Apr 2019 17:15:36 -0700 Subject: [PATCH 276/342] add thread-safe KeyValueCache --- ModuleManager/Collections/KeyValueCache.cs | 29 ++++++++++ ModuleManager/ModuleManager.csproj | 1 + .../Collections/KeyValueCacheTest.cs | 53 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 4 files changed, 84 insertions(+) create mode 100644 ModuleManager/Collections/KeyValueCache.cs create mode 100644 ModuleManagerTests/Collections/KeyValueCacheTest.cs diff --git a/ModuleManager/Collections/KeyValueCache.cs b/ModuleManager/Collections/KeyValueCache.cs new file mode 100644 index 00000000..0aaee61b --- /dev/null +++ b/ModuleManager/Collections/KeyValueCache.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace ModuleManager.Collections +{ + public class KeyValueCache + { + private readonly Dictionary dict = new Dictionary(); + private readonly object lockObject = new object(); + + public TValue Fetch(TKey key, Func createValue) + { + if (createValue == null) throw new ArgumentNullException(nameof(createValue)); + lock(lockObject) + { + if (dict.TryGetValue(key, out TValue value)) + { + return value; + } + else + { + TValue newValue = createValue(); + dict.Add(key, newValue); + return newValue; + } + } + } + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 92ce283a..c4913ef7 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -38,6 +38,7 @@ + diff --git a/ModuleManagerTests/Collections/KeyValueCacheTest.cs b/ModuleManagerTests/Collections/KeyValueCacheTest.cs new file mode 100644 index 00000000..4fc5a9e6 --- /dev/null +++ b/ModuleManagerTests/Collections/KeyValueCacheTest.cs @@ -0,0 +1,53 @@ +using System; +using Xunit; +using ModuleManager.Collections; + +namespace ModuleManagerTests.Collections +{ + public class KeyValueCacheTest + { + [Fact] + public void TestFetch__CreateValueNull() + { + KeyValueCache cache = new KeyValueCache(); + ArgumentNullException ex = Assert.Throws(delegate + { + cache.Fetch(new object(), null); + }); + + Assert.Equal("createValue", ex.ParamName); + } + + [Fact] + public void TestFetch__KeyNotPresent() + { + object key = new object(); + object value = new object(); + KeyValueCache cache = new KeyValueCache(); + + object fetchedValue = cache.Fetch(key, () => value); + + Assert.Same(value, fetchedValue); + } + + [Fact] + public void TestFetch__KeyPresent() + { + object key = new object(); + object value = new object(); + KeyValueCache cache = new KeyValueCache(); + + cache.Fetch(key, () => value); + + bool called2ndTime = false; + object fetchedValue = cache.Fetch(key, delegate + { + called2ndTime = true; + return null; + }); + + Assert.Same(value, fetchedValue); + Assert.False(called2ndTime); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7e1bd948..d9aabe53 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -110,6 +110,7 @@ + From 6e6a465c4a04578772eb989b9f2132b3dd4b4d56 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 19 Apr 2019 17:34:03 -0700 Subject: [PATCH 277/342] use new KeyValueCache class as regex cache Makes it thread safe - doesn't matter in production but might matter in tests which can be run in parallel --- ModuleManager/MMPatchLoader.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index f3e0ab91..4ab1827a 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -37,7 +37,7 @@ public class MMPatchLoader public static bool keepPartDB = false; - private static readonly Dictionary regexCache = new Dictionary(); + private static readonly KeyValueCache regexCache = new KeyValueCache(); private string configSha; private Dictionary filesSha = new Dictionary(); @@ -1447,14 +1447,10 @@ private static string FindAndReplaceValue( { string[] split = value.Split(value[0]); - Regex replace; - if (regexCache.ContainsKey(split[1])) - replace = regexCache[split[1]]; - else + Regex replace = regexCache.Fetch(split[1], delegate { - replace = new Regex(split[1], RegexOptions.None); - regexCache.Add(split[1], replace); - } + return new Regex(split[1]); + }); value = replace.Replace(oValue, split[2]); } @@ -1659,11 +1655,10 @@ public static bool WildcardMatch(string s, string wildcard) return true; string pattern = "^" + Regex.Escape(wildcard).Replace(@"\*", ".*").Replace(@"\?", ".") + "$"; - if (!regexCache.TryGetValue(pattern, out Regex regex)) + Regex regex = regexCache.Fetch(pattern, delegate { - regex = new Regex(pattern); - regexCache.Add(pattern, regex); - } + return new Regex(pattern); + }); return regex.IsMatch(s); } From a0c1dfc2c7342c8ee1010bee9a3019a3d4eb2b31 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 19 Apr 2019 16:33:05 -0700 Subject: [PATCH 278/342] add test for ProtoUrlConfig --- ModuleManagerTests/ModuleManagerTests.csproj | 1 + ModuleManagerTests/ProtoUrlConfigTest.cs | 74 ++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 ModuleManagerTests/ProtoUrlConfigTest.cs diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index d9aabe53..a550a59a 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -108,6 +108,7 @@ + diff --git a/ModuleManagerTests/ProtoUrlConfigTest.cs b/ModuleManagerTests/ProtoUrlConfigTest.cs new file mode 100644 index 00000000..9a1cfcdb --- /dev/null +++ b/ModuleManagerTests/ProtoUrlConfigTest.cs @@ -0,0 +1,74 @@ +using System; +using Xunit; +using TestUtils; +using ModuleManager; + +namespace ModuleManagerTests +{ + public class ProtoUrlConfigTest + { + [Fact] + public void TestContructor__UrlFileNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new ProtoUrlConfig(null, new ConfigNode()); + }); + + Assert.Equal("urlFile", ex.ParamName); + } + + [Fact] + public void TestContructor__NodeNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new ProtoUrlConfig(UrlBuilder.CreateFile("foo/bar"), null); + }); + + Assert.Equal("node", ex.ParamName); + } + + [Fact] + public void TestUrlFile() + { + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("abc/def.cfg"); + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(urlFile, new ConfigNode()); + + Assert.Same(urlFile, protoUrlConfig.UrlFile); + } + + [Fact] + public void TestNode() + { + ConfigNode node = new ConfigNode("NODE"); + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("foo/bar"), node); + + Assert.Same(node, protoUrlConfig.Node); + } + + [Fact] + public void TestFileUrl() + { + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("abc/def.cfg"), new ConfigNode()); + + Assert.Equal("abc/def.cfg", protoUrlConfig.FileUrl); + } + + [Fact] + public void TestNodeType() + { + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("abc/def"), new ConfigNode("SOME_NODE")); + + Assert.Equal("SOME_NODE", protoUrlConfig.NodeType); + } + + [Fact] + public void TestFullUrl() + { + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("abc/def.cfg"), new ConfigNode("SOME_NODE")); + + Assert.Equal("abc/def.cfg/SOME_NODE", protoUrlConfig.FullUrl); + } + } +} From 72717253e9d96fa05a9ca19603b8dbbe3964c444 Mon Sep 17 00:00:00 2001 From: blowfish Date: Fri, 19 Apr 2019 20:15:10 -0700 Subject: [PATCH 279/342] Add name value to applying patch messages Makes debugging patches easier, since multiple root nodes can save the same url Resolves #143 --- ModuleManager/ProtoUrlConfig.cs | 3 +++ ModuleManagerTests/ProtoUrlConfigTest.cs | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/ModuleManager/ProtoUrlConfig.cs b/ModuleManager/ProtoUrlConfig.cs index 74533948..71f39df3 100644 --- a/ModuleManager/ProtoUrlConfig.cs +++ b/ModuleManager/ProtoUrlConfig.cs @@ -29,6 +29,9 @@ public ProtoUrlConfig(UrlDir.UrlFile urlFile, ConfigNode node) Node = node ?? throw new ArgumentNullException(nameof(node)); FileUrl = UrlFile.url + '.' + urlFile.fileExtension; FullUrl = FileUrl + '/' + Node.name; + + if (node.GetValue("name") is string nameValue) + FullUrl += '[' + nameValue + ']'; } } } diff --git a/ModuleManagerTests/ProtoUrlConfigTest.cs b/ModuleManagerTests/ProtoUrlConfigTest.cs index 9a1cfcdb..f16cd1bd 100644 --- a/ModuleManagerTests/ProtoUrlConfigTest.cs +++ b/ModuleManagerTests/ProtoUrlConfigTest.cs @@ -70,5 +70,18 @@ public void TestFullUrl() Assert.Equal("abc/def.cfg/SOME_NODE", protoUrlConfig.FullUrl); } + + [Fact] + public void TestFullUrl__NameValue() + { + ConfigNode node = new TestConfigNode("SOME_NODE") + { + { "name", "some_value" }, + }; + + ProtoUrlConfig protoUrlConfig = new ProtoUrlConfig(UrlBuilder.CreateFile("abc/def.cfg"), node); + + Assert.Equal("abc/def.cfg/SOME_NODE[some_value]", protoUrlConfig.FullUrl); + } } } From cfaac6aa5031db26d8d84216f5e6885314a10306 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 29 Apr 2019 21:25:15 -0700 Subject: [PATCH 280/342] Fix StreamLogger ignoring messages Resolves #145 --- ModuleManager/Logging/StreamLogger.cs | 4 ++- .../Logging/StreamLoggerTest.cs | 31 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index 8f4b131a..6e157698 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -42,7 +42,9 @@ public void Log(LogType logType, string message) public void Exception(string message, Exception exception) { - Log(LogType.Exception, exception?.ToString() ?? ""); + if (!string.IsNullOrEmpty(message)) message += ": "; + message += exception?.ToString() ?? ""; + Log(LogType.Exception, message); } public void Dispose() diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index fdcb832c..f577adae 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -147,12 +147,13 @@ public void TestLog__Error() [Fact] public void TestLog__Exception() { - byte[] bytes = new byte[50]; + Exception ex = new Exception("something went wrong"); + byte[] bytes = new byte[100]; using (MemoryStream stream = new MemoryStream(bytes, true)) { using (StreamLogger streamLogger = new StreamLogger(stream)) { - streamLogger.Log(LogType.Exception, "a message"); + streamLogger.Exception("a message", ex); } } @@ -162,7 +163,31 @@ public void TestLog__Exception() { string result = reader.ReadToEnd(); Assert.Contains("[EXC ", result); - Assert.Contains("] a message", result); + Assert.Contains("] a message: " + ex.ToString(), result); + } + } + } + + [Fact] + public void TestLog__Exception__NullMessage() + { + Exception ex = new Exception("something went wrong"); + byte[] bytes = new byte[100]; + using (MemoryStream stream = new MemoryStream(bytes, true)) + { + using (StreamLogger streamLogger = new StreamLogger(stream)) + { + streamLogger.Exception(null, ex); + } + } + + using (MemoryStream stream = new MemoryStream(bytes, false)) + { + using (StreamReader reader = new StreamReader(stream)) + { + string result = reader.ReadToEnd(); + Assert.Contains("[EXC ", result); + Assert.Contains("] " + ex.ToString(), result); } } } From d4d0fb78126ad4979c7df55f47dd4e9fc1e55b9b Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 00:24:38 -0700 Subject: [PATCH 281/342] Redo logging interface * Convert exceptions to strings rather than keeping a separate interface for exceptions everywhere * Log messages know how to convert themselves to log strings * Simplified interface (including assertions) provided by extension methods --- .../Extensions/IBasicLoggerExtensions.cs | 19 ++- ModuleManager/Logging/ExceptionMessage.cs | 21 --- ModuleManager/Logging/IBasicLogger.cs | 4 +- ModuleManager/Logging/ILogMessage.cs | 5 +- ModuleManager/Logging/LogMessage.cs | 43 +++++ ModuleManager/Logging/LogSplitter.cs | 14 +- ModuleManager/Logging/ModLogger.cs | 14 +- ModuleManager/Logging/NormalMessage.cs | 22 --- ModuleManager/Logging/QueueLogRunner.cs | 4 +- ModuleManager/Logging/QueueLogger.cs | 10 +- ModuleManager/Logging/StreamLogger.cs | 29 +--- ModuleManager/Logging/UnityLogger.cs | 9 +- ModuleManager/ModuleManager.csproj | 3 +- .../Extensions/IBasicLoggerExtensionsTest.cs | 55 ++++++- ModuleManagerTests/InGameTestRunnerTest.cs | 24 +-- .../Logging/ExceptionMessageTest.cs | 22 --- ModuleManagerTests/Logging/LogMessageTest.cs | 70 ++++++++ ModuleManagerTests/Logging/LogSplitterTest.cs | 23 +-- ModuleManagerTests/Logging/ModLoggerTest.cs | 30 ++-- .../Logging/NormalMessageTest.cs | 37 ----- .../Logging/QueueLogRunnerTest.cs | 12 +- ModuleManagerTests/Logging/QueueLoggerTest.cs | 36 ++--- .../Logging/StreamLoggerTest.cs | 153 ++---------------- ModuleManagerTests/Logging/UnityLoggerTest.cs | 24 ++- ModuleManagerTests/LoggingAssertionHelpers.cs | 70 ++++++++ ModuleManagerTests/MMPatchLoaderTest.cs | 6 +- ModuleManagerTests/ModuleManagerTests.csproj | 4 +- ModuleManagerTests/PatchApplierTest.cs | 6 +- ModuleManagerTests/Patches/EditPatchTest.cs | 2 +- .../Progress/PatchProgressTest.cs | 60 +++---- 30 files changed, 406 insertions(+), 425 deletions(-) delete mode 100644 ModuleManager/Logging/ExceptionMessage.cs create mode 100644 ModuleManager/Logging/LogMessage.cs delete mode 100644 ModuleManager/Logging/NormalMessage.cs delete mode 100644 ModuleManagerTests/Logging/ExceptionMessageTest.cs create mode 100644 ModuleManagerTests/Logging/LogMessageTest.cs delete mode 100644 ModuleManagerTests/Logging/NormalMessageTest.cs create mode 100644 ModuleManagerTests/LoggingAssertionHelpers.cs diff --git a/ModuleManager/Extensions/IBasicLoggerExtensions.cs b/ModuleManager/Extensions/IBasicLoggerExtensions.cs index 46c4c5a4..9fd29be3 100644 --- a/ModuleManager/Extensions/IBasicLoggerExtensions.cs +++ b/ModuleManager/Extensions/IBasicLoggerExtensions.cs @@ -6,8 +6,21 @@ namespace ModuleManager.Extensions { public static class IBasicLoggerExtensions { - public static void Info(this IBasicLogger logger, string message) => logger.Log(LogType.Log, message); - public static void Warning(this IBasicLogger logger, string message) => logger.Log(LogType.Warning, message); - public static void Error(this IBasicLogger logger, string message) => logger.Log(LogType.Error, message); + public static void Info(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Log, message)); + public static void Warning(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Warning, message)); + public static void Error(this IBasicLogger logger, string message) => logger.Log(new LogMessage(LogType.Error, message)); + + public static void Exception(this IBasicLogger logger, Exception exception) + { + if (exception == null) throw new ArgumentNullException(nameof(exception)); + logger.Log(new LogMessage(LogType.Exception, exception.ToString())); + } + + public static void Exception(this IBasicLogger logger, string message, Exception exception) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + if (exception == null) throw new ArgumentNullException(nameof(exception)); + logger.Log(new LogMessage(LogType.Exception, message + ": " + exception.ToString())); + } } } diff --git a/ModuleManager/Logging/ExceptionMessage.cs b/ModuleManager/Logging/ExceptionMessage.cs deleted file mode 100644 index fceb166d..00000000 --- a/ModuleManager/Logging/ExceptionMessage.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace ModuleManager.Logging -{ - public class ExceptionMessage : ILogMessage - { - public readonly string message; - public readonly Exception exception; - - public ExceptionMessage(string message, Exception exception) - { - this.message = message; - this.exception = exception; - } - - public void LogTo(IBasicLogger logger) - { - logger.Exception(message, exception); - } - } -} diff --git a/ModuleManager/Logging/IBasicLogger.cs b/ModuleManager/Logging/IBasicLogger.cs index c2fdd85e..25cd9367 100644 --- a/ModuleManager/Logging/IBasicLogger.cs +++ b/ModuleManager/Logging/IBasicLogger.cs @@ -1,12 +1,10 @@ using System; -using UnityEngine; namespace ModuleManager.Logging { // Stripped down version of UnityEngine.ILogger public interface IBasicLogger { - void Log(LogType logType, string message); - void Exception(string message, Exception exception); + void Log(ILogMessage message); } } diff --git a/ModuleManager/Logging/ILogMessage.cs b/ModuleManager/Logging/ILogMessage.cs index 3193f973..55137cc7 100644 --- a/ModuleManager/Logging/ILogMessage.cs +++ b/ModuleManager/Logging/ILogMessage.cs @@ -1,9 +1,12 @@ using System; +using UnityEngine; namespace ModuleManager.Logging { public interface ILogMessage { - void LogTo(IBasicLogger logger); + LogType LogType { get; } + string Message { get; } + string ToLogString(); } } diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs new file mode 100644 index 00000000..53f3c0f8 --- /dev/null +++ b/ModuleManager/Logging/LogMessage.cs @@ -0,0 +1,43 @@ +using System; +using UnityEngine; + +namespace ModuleManager.Logging +{ + public class LogMessage : ILogMessage + { + private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; + + public LogType LogType { get; } + public string Message { get; } + + public LogMessage(LogType logType, string message) + { + LogType = logType; + Message = message ?? throw new ArgumentNullException(nameof(message)); + } + + public string ToLogString() + { + string prefix; + if (LogType == LogType.Log) + prefix = "LOG"; + else if (LogType == LogType.Warning) + prefix = "WRN"; + else if (LogType == LogType.Error) + prefix = "ERR"; + else if (LogType == LogType.Assert) + prefix = "AST"; + else if (LogType == LogType.Exception) + prefix = "EXC"; + else + prefix = "???"; + + return $"[{prefix} {DateTime.Now.ToString(DATETIME_FORMAT_STRING)}] {Message}"; + } + + public override string ToString() + { + return $"[{GetType().FullName} LogType={LogType} Message={Message}]"; + } + } +} diff --git a/ModuleManager/Logging/LogSplitter.cs b/ModuleManager/Logging/LogSplitter.cs index 53712f20..b1d2c89f 100644 --- a/ModuleManager/Logging/LogSplitter.cs +++ b/ModuleManager/Logging/LogSplitter.cs @@ -1,5 +1,4 @@ using System; -using UnityEngine; namespace ModuleManager.Logging { @@ -14,16 +13,11 @@ public LogSplitter(IBasicLogger logger1, IBasicLogger logger2) this.logger2 = logger2 ?? throw new ArgumentNullException(nameof(logger2)); } - public void Log(LogType logType, string message) + public void Log(ILogMessage message) { - logger1.Log(logType, message); - logger2.Log(logType, message); - } - - public void Exception(string message, Exception exception) - { - logger1.Exception(message, exception); - logger2.Exception(message, exception); + if (message == null) throw new ArgumentNullException(nameof(message)); + logger1.Log(message); + logger2.Log(message); } } } diff --git a/ModuleManager/Logging/ModLogger.cs b/ModuleManager/Logging/ModLogger.cs index eb2d3699..89a05066 100644 --- a/ModuleManager/Logging/ModLogger.cs +++ b/ModuleManager/Logging/ModLogger.cs @@ -1,21 +1,23 @@ using System; -using UnityEngine; namespace ModuleManager.Logging { public class ModLogger : IBasicLogger { - private string prefix; - private IBasicLogger logger; + private readonly string prefix; + private readonly IBasicLogger logger; public ModLogger(string prefix, IBasicLogger logger) { if (string.IsNullOrEmpty(prefix)) throw new ArgumentNullException(nameof(prefix)); - this.prefix = "[" + prefix + "] "; + this.prefix = $"[{prefix}] "; this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public void Log(LogType logType, string message) => logger.Log(logType, prefix + message); - public void Exception(string message, Exception exception) => logger.Exception(prefix + message, exception); + public void Log(ILogMessage message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + logger.Log(new LogMessage(message.LogType, prefix + message.Message)); + } } } diff --git a/ModuleManager/Logging/NormalMessage.cs b/ModuleManager/Logging/NormalMessage.cs deleted file mode 100644 index 549f9aa8..00000000 --- a/ModuleManager/Logging/NormalMessage.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using UnityEngine; - -namespace ModuleManager.Logging -{ - public class NormalMessage : ILogMessage - { - public readonly LogType logType; - public readonly string message; - - public NormalMessage(LogType logType, string message) - { - this.logType = logType; - this.message = message; - } - - public void LogTo(IBasicLogger logger) - { - logger.Log(logType, message); - } - } -} diff --git a/ModuleManager/Logging/QueueLogRunner.cs b/ModuleManager/Logging/QueueLogRunner.cs index 2ed5c300..f78cf611 100644 --- a/ModuleManager/Logging/QueueLogRunner.cs +++ b/ModuleManager/Logging/QueueLogRunner.cs @@ -47,7 +47,7 @@ public void Run(IBasicLogger logger) foreach (ILogMessage message in logQueue.TakeAll()) { - message.LogTo(logger); + logger.Log(message); } long timeRemaining = timeToWaitForLogsMs - stopwatch.ElapsedMilliseconds; @@ -59,7 +59,7 @@ public void Run(IBasicLogger logger) foreach (ILogMessage message in logQueue.TakeAll()) { - message.LogTo(logger); + logger.Log(message); } state = State.Stopped; diff --git a/ModuleManager/Logging/QueueLogger.cs b/ModuleManager/Logging/QueueLogger.cs index 82d0bb8e..b7cd288a 100644 --- a/ModuleManager/Logging/QueueLogger.cs +++ b/ModuleManager/Logging/QueueLogger.cs @@ -1,5 +1,4 @@ using System; -using UnityEngine; using ModuleManager.Collections; namespace ModuleManager.Logging @@ -10,10 +9,13 @@ public class QueueLogger : IBasicLogger public QueueLogger(IMessageQueue queue) { - this.queue = queue; + this.queue = queue ?? throw new ArgumentNullException(nameof(queue)); } - public void Log(LogType logType, string message) => queue.Add(new NormalMessage(logType, message)); - public void Exception(string message, Exception exception) => queue.Add(new ExceptionMessage(message, exception)); + public void Log(ILogMessage message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + queue.Add(message); + } } } diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index 6e157698..bfbb1053 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -1,13 +1,10 @@ using System; using System.IO; -using UnityEngine; namespace ModuleManager.Logging { public class StreamLogger : IBasicLogger, IDisposable { - private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; - private readonly Stream stream; private readonly StreamWriter streamWriter; private bool disposed = false; @@ -19,32 +16,12 @@ public StreamLogger(Stream stream) streamWriter = new StreamWriter(stream); } - public void Log(LogType logType, string message) + public void Log(ILogMessage message) { if (disposed) throw new InvalidOperationException("Object has already been disposed"); + if (message == null) throw new ArgumentNullException(nameof(message)); - string prefix; - if (logType == LogType.Log) - prefix = "LOG"; - else if (logType == LogType.Warning) - prefix = "WRN"; - else if (logType == LogType.Error) - prefix = "ERR"; - else if (logType == LogType.Assert) - prefix = "AST"; - else if (logType == LogType.Exception) - prefix = "EXC"; - else - prefix = "UNK"; - - streamWriter.WriteLine("[{0} {1}] {2}", prefix, DateTime.Now.ToString(DATETIME_FORMAT_STRING), message); - } - - public void Exception(string message, Exception exception) - { - if (!string.IsNullOrEmpty(message)) message += ": "; - message += exception?.ToString() ?? ""; - Log(LogType.Exception, message); + streamWriter.WriteLine(message.ToLogString()); } public void Dispose() diff --git a/ModuleManager/Logging/UnityLogger.cs b/ModuleManager/Logging/UnityLogger.cs index 0655b807..b31c1003 100644 --- a/ModuleManager/Logging/UnityLogger.cs +++ b/ModuleManager/Logging/UnityLogger.cs @@ -1,6 +1,5 @@ using System; using UnityEngine; -using ModuleManager.Extensions; namespace ModuleManager.Logging { @@ -13,12 +12,10 @@ public UnityLogger(ILogger logger) this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public void Log(LogType logType, string message) => logger.Log(logType, message); - - public void Exception(string message, Exception exception) + public void Log(ILogMessage message) { - this.Error(message); - logger.LogException(exception); + if (message == null) throw new ArgumentNullException(nameof(message)); + logger.Log(message.LogType, message.Message); } } } diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index c4913ef7..1a2d77f5 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -47,13 +47,12 @@ - - + diff --git a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs index 781cccb6..f6c1fad7 100644 --- a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs @@ -20,21 +20,70 @@ public IBasicLoggerExtensionsTest() public void TestInfo() { logger.Info("well hi there"); - logger.Received().Log(LogType.Log, "well hi there"); + logger.AssertInfo("well hi there"); } [Fact] public void TestWarning() { logger.Warning("I'm warning you"); - logger.Received().Log(LogType.Warning, "I'm warning you"); + logger.AssertWarning("I'm warning you"); } [Fact] public void TestError() { logger.Error("You have made a grave mistake"); - logger.Received().Log(LogType.Error, "You have made a grave mistake"); + logger.AssertError("You have made a grave mistake"); + } + + [Fact] + public void TestException() + { + Exception ex = new Exception(); + logger.Exception(ex); + logger.AssertException(ex); + } + + [Fact] + public void TestException__Null() + { + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Exception(null); + }); + + Assert.Equal("exception", ex.ParamName); + } + + [Fact] + public void TestException__Message() + { + Exception ex = new Exception(); + logger.Exception("a message", ex); + logger.AssertException("a message", ex); + } + + [Fact] + public void TestException__Message__MessageNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Exception(null, new Exception()); + }); + + Assert.Equal("message", ex.ParamName); + } + + [Fact] + public void TestException__Message__ExceptionNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Exception("a message", null); + }); + + Assert.Equal("exception", ex.ParamName); } } } diff --git a/ModuleManagerTests/InGameTestRunnerTest.cs b/ModuleManagerTests/InGameTestRunnerTest.cs index cd860bc2..a75897f4 100644 --- a/ModuleManagerTests/InGameTestRunnerTest.cs +++ b/ModuleManagerTests/InGameTestRunnerTest.cs @@ -73,12 +73,12 @@ public void TestRunTestCases__WrongNumberOfNodes() Received.InOrder(delegate { - logger.Log(LogType.Log, "Running tests..."); - logger.Log(LogType.Error, $"Test blah1 failed as expected number of nodes differs expected: 1 found: 2"); - logger.Log(LogType.Log, testNode1.ToString()); - logger.Log(LogType.Log, testNode2.ToString()); - logger.Log(LogType.Log, expectNode.ToString()); - logger.Log(LogType.Log, "tests complete."); + logger.AssertInfo("Running tests..."); + logger.AssertError($"Test blah1 failed as expected number of nodes differs expected: 1 found: 2"); + logger.AssertInfo(testNode1.ToString()); + logger.AssertInfo(testNode2.ToString()); + logger.AssertInfo(expectNode.ToString()); + logger.AssertInfo("tests complete."); }); Assert.Equal(3, file1.configs.Count); @@ -131,11 +131,11 @@ public void TestRunTestCases__AllPassing() Received.InOrder(delegate { - logger.Log(LogType.Log, "Running tests..."); - logger.Log(LogType.Log, "tests complete."); + logger.AssertInfo("Running tests..."); + logger.AssertInfo("tests complete."); }); - logger.DidNotReceive().Log(LogType.Error, Arg.Any()); + logger.AssertNoError(); Assert.Empty(file1.configs); Assert.Empty(file2.configs); @@ -176,9 +176,9 @@ public void TestRunTestCases__Failure() Received.InOrder(delegate { - logger.Log(LogType.Log, "Running tests..."); - logger.Log(LogType.Error, $"Test blah1[0] failed as expected output and actual output differ.\nexpected:\n{expectNode1}\nActually got:\n{testNode1}"); - logger.Log(LogType.Log, "tests complete."); + logger.AssertInfo("Running tests..."); + logger.AssertError($"Test blah1[0] failed as expected output and actual output differ.\nexpected:\n{expectNode1}\nActually got:\n{testNode1}"); + logger.AssertInfo("tests complete."); }); diff --git a/ModuleManagerTests/Logging/ExceptionMessageTest.cs b/ModuleManagerTests/Logging/ExceptionMessageTest.cs deleted file mode 100644 index b072ce89..00000000 --- a/ModuleManagerTests/Logging/ExceptionMessageTest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Xunit; -using NSubstitute; -using ModuleManager.Logging; - -namespace ModuleManagerTests.Logging -{ - public class ExceptionMessageTest - { - [Fact] - public void TestLogTo() - { - IBasicLogger logger = Substitute.For(); - - Exception e = new Exception(); - ExceptionMessage message = new ExceptionMessage("An exception was thrown", e); - message.LogTo(logger); - - logger.Received().Exception("An exception was thrown", e); - } - } -} diff --git a/ModuleManagerTests/Logging/LogMessageTest.cs b/ModuleManagerTests/Logging/LogMessageTest.cs new file mode 100644 index 00000000..0a0d42c3 --- /dev/null +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -0,0 +1,70 @@ +using System; +using Xunit; +using UnityEngine; +using ModuleManager.Logging; + +namespace ModuleManagerTests.Logging +{ + public class LogMessageTest + { + [Fact] + public void TestConstructor__NullMessage() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogMessage(LogType.Log, null); + }); + + Assert.Equal("message", ex.ParamName); + } + + [Fact] + public void TestToLogMessage__Info() + { + LogMessage message = new LogMessage(LogType.Log, "everything is ok"); + Assert.Matches(@"^\[LOG \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] everything is ok$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Warning() + { + LogMessage message = new LogMessage(LogType.Warning, "I'm warning you"); + Assert.Matches(@"^\[WRN \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] I'm warning you$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Error() + { + LogMessage message = new LogMessage(LogType.Error, "You went too far"); + Assert.Matches(@"^\[ERR \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Exception() + { + LogMessage message = new LogMessage(LogType.Exception, "You went too far"); + Assert.Matches(@"^\[EXC \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Assert() + { + LogMessage message = new LogMessage(LogType.Assert, "You went too far"); + Assert.Matches(@"^\[AST \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + } + + [Fact] + public void TestToLogMessage__Unknown() + { + LogMessage message = new LogMessage((LogType)9999, "You went too far"); + Assert.Matches(@"^\[\?\?\? \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + } + + [Fact] + public void TestToString() + { + LogMessage message = new LogMessage(LogType.Log, "everything is ok"); + Assert.Equal("[ModuleManager.Logging.LogMessage LogType=Log Message=everything is ok]", message.ToString()); + } + } +} diff --git a/ModuleManagerTests/Logging/LogSplitterTest.cs b/ModuleManagerTests/Logging/LogSplitterTest.cs index 2375a3a3..4135f92d 100644 --- a/ModuleManagerTests/Logging/LogSplitterTest.cs +++ b/ModuleManagerTests/Logging/LogSplitterTest.cs @@ -36,21 +36,22 @@ public void TestLog() IBasicLogger logger1 = Substitute.For(); IBasicLogger logger2 = Substitute.For(); LogSplitter logSplitter = new LogSplitter(logger1, logger2); - logSplitter.Log(LogType.Log, "some stuff"); - logger1.Received().Log(LogType.Log, "some stuff"); - logger2.Received().Log(LogType.Log, "some stuff"); + ILogMessage message = Substitute.For(); + logSplitter.Log(message); + logger1.Received().Log(message); + logger2.Received().Log(message); } [Fact] - public void TestException() + public void TestLog__MessageNull() { - IBasicLogger logger1 = Substitute.For(); - IBasicLogger logger2 = Substitute.For(); - LogSplitter logSplitter = new LogSplitter(logger1, logger2); - Exception ex = new Exception(); - logSplitter.Exception("some stuff", ex); - logger1.Received().Exception("some stuff", ex); - logger2.Received().Exception("some stuff", ex); + LogSplitter logSplitter = new LogSplitter(Substitute.For(), Substitute.For()); + ArgumentNullException ex = Assert.Throws(delegate + { + logSplitter.Log(null); + }); + + Assert.Equal("message", ex.ParamName); } } } diff --git a/ModuleManagerTests/Logging/ModLoggerTest.cs b/ModuleManagerTests/Logging/ModLoggerTest.cs index 7c4bf427..0ca45679 100644 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ b/ModuleManagerTests/Logging/ModLoggerTest.cs @@ -1,7 +1,7 @@ using System; using Xunit; using NSubstitute; -using UnityEngine; +using ModuleManager.Extensions; using ModuleManager.Logging; namespace ModuleManagerTests.Logging @@ -51,36 +51,30 @@ public void TestConstructor__LoggerNull() } [Fact] - public void TestLog__Info() + public void TestLog() { - logger.Log(LogType.Log, "well hi there"); + logger.Info("well hi there"); - innerLogger.Received().Log(LogType.Log, "[MyMod] well hi there"); + innerLogger.AssertInfo("[MyMod] well hi there"); } [Fact] public void TestLog__Warning() { - logger.Log(LogType.Warning, "I'm warning you"); + logger.Warning("I'm warning you"); - innerLogger.Received().Log(LogType.Warning, "[MyMod] I'm warning you"); + innerLogger.AssertWarning("[MyMod] I'm warning you"); } [Fact] - public void TestLog__Error() + public void TestLog__Null() { - logger.Log(LogType.Error, "You have made a grave mistake"); - - innerLogger.Received().Log(LogType.Error, "[MyMod] You have made a grave mistake"); - } + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Log(null); + }); - [Fact] - public void TestException() - { - Exception e = new Exception(); - logger.Exception("An exception was thrown", e); - - innerLogger.Received().Exception("[MyMod] An exception was thrown", e); + Assert.Equal("message", ex.ParamName); } } } diff --git a/ModuleManagerTests/Logging/NormalMessageTest.cs b/ModuleManagerTests/Logging/NormalMessageTest.cs deleted file mode 100644 index c63dd985..00000000 --- a/ModuleManagerTests/Logging/NormalMessageTest.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Xunit; -using NSubstitute; -using UnityEngine; -using ModuleManager.Logging; - -namespace ModuleManagerTests.Logging -{ - public class NormalMessageTest - { - private IBasicLogger logger = Substitute.For(); - - [Fact] - public void TestLogTo__Info() - { - NormalMessage message = new NormalMessage(LogType.Log, "everything is ok"); - message.LogTo(logger); - logger.Received().Log(LogType.Log, "everything is ok"); - } - - [Fact] - public void TestLogTo__Warning() - { - NormalMessage message = new NormalMessage(LogType.Warning, "I'm warning you"); - message.LogTo(logger); - logger.Received().Log(LogType.Warning, "I'm warning you"); - } - - [Fact] - public void TestLogTo__Error() - { - NormalMessage message = new NormalMessage(LogType.Error, "You went too far"); - message.LogTo(logger); - logger.Received().Log(LogType.Error, "You went too far"); - } - } -} diff --git a/ModuleManagerTests/Logging/QueueLogRunnerTest.cs b/ModuleManagerTests/Logging/QueueLogRunnerTest.cs index a52e7add..6a975bd2 100644 --- a/ModuleManagerTests/Logging/QueueLogRunnerTest.cs +++ b/ModuleManagerTests/Logging/QueueLogRunnerTest.cs @@ -73,12 +73,12 @@ public void TestRun() Received.InOrder(delegate { - message1.LogTo(logger); - message2.LogTo(logger); - message3.LogTo(logger); - message4.LogTo(logger); - message5.LogTo(logger); - message6.LogTo(logger); + logger.Log(message1); + logger.Log(message2); + logger.Log(message3); + logger.Log(message4); + logger.Log(message5); + logger.Log(message6); }); } diff --git a/ModuleManagerTests/Logging/QueueLoggerTest.cs b/ModuleManagerTests/Logging/QueueLoggerTest.cs index b984f8c2..dc8a1dfd 100644 --- a/ModuleManagerTests/Logging/QueueLoggerTest.cs +++ b/ModuleManagerTests/Logging/QueueLoggerTest.cs @@ -1,7 +1,6 @@ using System; using Xunit; using NSubstitute; -using UnityEngine; using ModuleManager.Collections; using ModuleManager.Logging; @@ -19,33 +18,34 @@ public QueueLoggerTest() } [Fact] - public void TestLog__Info() + public void TestConstructor__QueueNull() { - logger.Log(LogType.Log, "useful information"); - queue.Received().Add(Arg.Is(m => m.logType == LogType.Log && m.message == "useful information")); - } + ArgumentNullException ex = Assert.Throws(delegate + { + new QueueLogger(null); + }); - [Fact] - public void TestLog__Warning() - { - logger.Log(LogType.Warning, "not to alarm you, but something might be wrong"); - queue.Received().Add(Arg.Is(m => m.logType == LogType.Warning && m.message == "not to alarm you, but something might be wrong")); + Assert.Equal("queue", ex.ParamName); } [Fact] - public void TestLog__Error() + public void TestLog() { - logger.Log(LogType.Error, "you broke everything"); - queue.Received().Add(Arg.Is(m => m.logType == LogType.Error && m.message == "you broke everything")); + ILogMessage message = Substitute.For(); + logger.Log(message); + queue.Received().Add(message); } - [Fact] - public void TestException() + public void TestLog__MessageNull() { - Exception e = new Exception(); - logger.Exception("An exception was thrown", e); - queue.Received().Add(Arg.Is(m => m.message == "An exception was thrown" && m.exception == e)); + ArgumentNullException ex = Assert.Throws(delegate + { + logger.Log(null); + }); + + Assert.Equal("message", ex.ParamName); } + } } diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index f577adae..61535387 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -45,7 +45,7 @@ public void TestLog__AlreadyDisposed() InvalidOperationException ex = Assert.Throws(delegate { - streamLogger.Log(LogType.Log, "a message"); + streamLogger.Log(Substitute.For()); }); Assert.Contains("Object has already been disposed", ex.Message); @@ -53,14 +53,16 @@ public void TestLog__AlreadyDisposed() } [Fact] - public void TestLog__Log() + public void TestLog() { - byte[] bytes = new byte[50]; + ILogMessage message = Substitute.For(); + message.ToLogString().Returns("[OMG wtf] bbq"); + byte[] bytes = new byte[15]; using (MemoryStream stream = new MemoryStream(bytes, true)) { using (StreamLogger streamLogger = new StreamLogger(stream)) { - streamLogger.Log(LogType.Log, "a message"); + streamLogger.Log(message); } } @@ -68,150 +70,25 @@ public void TestLog__Log() { using (StreamReader reader = new StreamReader(stream)) { - string result = reader.ReadToEnd(); - Assert.Contains("[LOG ", result); - Assert.Contains("] a message", result); + string result = reader.ReadToEnd().Trim(); + Assert.Equal("[OMG wtf] bbq", result); } } } [Fact] - public void TestLog__Assert() + public void TestLog__MessageNull() { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Log(LogType.Assert, "a message"); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[AST ", result); - Assert.Contains("] a message", result); - } - } - } - - [Fact] - public void TestLog__Warning() - { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Log(LogType.Warning, "a message"); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[WRN ", result); - Assert.Contains("] a message", result); - } - } - } - - [Fact] - public void TestLog__Error() - { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Log(LogType.Error, "a message"); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[ERR ", result); - Assert.Contains("] a message", result); - } - } - } - - [Fact] - public void TestLog__Exception() - { - Exception ex = new Exception("something went wrong"); - byte[] bytes = new byte[100]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Exception("a message", ex); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[EXC ", result); - Assert.Contains("] a message: " + ex.ToString(), result); - } - } - } - - [Fact] - public void TestLog__Exception__NullMessage() - { - Exception ex = new Exception("something went wrong"); - byte[] bytes = new byte[100]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) - { - streamLogger.Exception(null, ex); - } - } - - using (MemoryStream stream = new MemoryStream(bytes, false)) + using (MemoryStream stream = new MemoryStream(new byte[0], true)) { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[EXC ", result); - Assert.Contains("] " + ex.ToString(), result); - } - } - } + StreamLogger streamLogger = new StreamLogger(stream); - [Fact] - public void TestLog__Unknown() - { - byte[] bytes = new byte[50]; - using (MemoryStream stream = new MemoryStream(bytes, true)) - { - using (StreamLogger streamLogger = new StreamLogger(stream)) + ArgumentNullException ex = Assert.Throws(delegate { - streamLogger.Log((LogType)1000, "a message"); - } - } + streamLogger.Log(null); + }); - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - using (StreamReader reader = new StreamReader(stream)) - { - string result = reader.ReadToEnd(); - Assert.Contains("[UNK ", result); - Assert.Contains("] a message", result); - } + Assert.Equal("message", ex.ParamName); } } } diff --git a/ModuleManagerTests/Logging/UnityLoggerTest.cs b/ModuleManagerTests/Logging/UnityLoggerTest.cs index b2e22d4e..5d820c25 100644 --- a/ModuleManagerTests/Logging/UnityLoggerTest.cs +++ b/ModuleManagerTests/Logging/UnityLoggerTest.cs @@ -2,6 +2,7 @@ using Xunit; using NSubstitute; using UnityEngine; +using ModuleManager.Extensions; using ModuleManager.Logging; namespace ModuleManagerTests.Logging @@ -31,7 +32,7 @@ public void TestConstructor__LoggerNull() [Fact] public void TestLog__Info() { - logger.Log(LogType.Log, "well hi there"); + logger.Info("well hi there"); innerLogger.Received().Log(LogType.Log, "well hi there"); } @@ -39,27 +40,20 @@ public void TestLog__Info() [Fact] public void TestLog__Warning() { - logger.Log(LogType.Warning, "I'm warning you"); + logger.Warning("I'm warning you"); innerLogger.Received().Log(LogType.Warning, "I'm warning you"); } [Fact] - public void TestLog__Error() + public void TestLog__MessageNull() { - logger.Log(LogType.Error, "You have made a grave mistake"); - - innerLogger.Received().Log(LogType.Error, "You have made a grave mistake"); - } - - [Fact] - public void TestException() - { - Exception e = new Exception(); - logger.Exception("An exception was thrown", e); + ArgumentNullException e = Assert.Throws(delegate + { + logger.Log(null); + }); - innerLogger.Received().Log(LogType.Error, "An exception was thrown"); - innerLogger.Received().LogException(e); + Assert.Equal("message", e.ParamName); } } } diff --git a/ModuleManagerTests/LoggingAssertionHelpers.cs b/ModuleManagerTests/LoggingAssertionHelpers.cs new file mode 100644 index 00000000..74ae5cc4 --- /dev/null +++ b/ModuleManagerTests/LoggingAssertionHelpers.cs @@ -0,0 +1,70 @@ +using System; +using UnityEngine; +using NSubstitute; +using ModuleManager.Logging; + +namespace ModuleManagerTests +{ + public static class LoggingAssertionHelpers + { + public static void AssertInfo(this IBasicLogger logger, string message) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Log && msg.Message == message)); + } + + public static void AssertNoInfo(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceive().Log(Arg.Is(msg => msg.LogType == LogType.Log)); + } + + public static void AssertWarning(this IBasicLogger logger, string message) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Warning && msg.Message == message)); + } + + public static void AssertNoWarning(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceive().Log(Arg.Is(msg => msg.LogType == LogType.Warning)); + } + + public static void AssertError(this IBasicLogger logger, string message) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Error && msg.Message == message)); + } + + public static void AssertNoError(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceive().Log(Arg.Is(msg => msg.LogType == LogType.Error)); + } + + public static void AssertException(this IBasicLogger logger, string message, Exception exception) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Exception && msg.Message == message + ": " + exception.ToString())); + } + + public static void AssertException(this IBasicLogger logger, Exception exception) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.Received().Log(Arg.Is(msg => msg.LogType == LogType.Exception && msg.Message == exception.ToString())); + } + + public static void AssertNoException(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceive().Log(Arg.Is(msg => msg.LogType == LogType.Exception)); + } + + public static void AssertNoLog(this IBasicLogger logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); + logger.DidNotReceiveWithAnyArgs().Log(null); + } + } +} diff --git a/ModuleManagerTests/MMPatchLoaderTest.cs b/ModuleManagerTests/MMPatchLoaderTest.cs index ac687909..29682448 100644 --- a/ModuleManagerTests/MMPatchLoaderTest.cs +++ b/ModuleManagerTests/MMPatchLoaderTest.cs @@ -82,9 +82,9 @@ private void EnsureNoErrors() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); - logger.DidNotReceive().Log(LogType.Error, Arg.Any()); - logger.DidNotReceive().Log(LogType.Exception, Arg.Any()); + logger.AssertNoWarning(); + logger.AssertNoError(); + logger.AssertNoException(); } } } diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index a550a59a..12379fd6 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -79,14 +79,14 @@ + - - + diff --git a/ModuleManagerTests/PatchApplierTest.cs b/ModuleManagerTests/PatchApplierTest.cs index d7fb0eb1..d54ac286 100644 --- a/ModuleManagerTests/PatchApplierTest.cs +++ b/ModuleManagerTests/PatchApplierTest.cs @@ -89,9 +89,9 @@ public void TestApplyPatches() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); - logger.DidNotReceive().Log(LogType.Warning, Arg.Any()); - logger.DidNotReceive().Log(LogType.Error, Arg.Any()); - logger.DidNotReceiveWithAnyArgs().Exception(null, null); + logger.AssertNoWarning(); + logger.AssertNoError(); + logger.AssertNoException(); Received.InOrder(delegate { diff --git a/ModuleManagerTests/Patches/EditPatchTest.cs b/ModuleManagerTests/Patches/EditPatchTest.cs index 50efd3b1..7c76cf48 100644 --- a/ModuleManagerTests/Patches/EditPatchTest.cs +++ b/ModuleManagerTests/Patches/EditPatchTest.cs @@ -236,7 +236,7 @@ public void TestApply__Loop() Received.InOrder(delegate { - logger.Log(LogType.Log, "Looping on ghi/jkl/@NODE to abc/def.cfg/NODE"); + logger.AssertInfo("Looping on ghi/jkl/@NODE to abc/def.cfg/NODE"); progress.ApplyingUpdate(urlConfig, patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[1], patch.UrlConfig); progress.ApplyingUpdate(modifiedUrlConfigs[2], patch.UrlConfig); diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index ee61fd46..8cde86c2 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -36,8 +36,8 @@ public void Test__Constructor__Nested() progress2.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); - logger2.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertNoLog(); + logger2.AssertInfo("Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -62,11 +62,11 @@ public void TestApplyingUpdate() progress.ApplyingUpdate(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying update ghi/jkl/@SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingUpdate(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying update pqr/stu/@SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying update pqr/stu/@SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -81,11 +81,11 @@ public void TesApplyingCopy() progress.ApplyingCopy(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying copy ghi/jkl/+SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying copy ghi/jkl/+SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingCopy(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying copy pqr/stu/+SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying copy pqr/stu/+SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -100,11 +100,11 @@ public void TesApplyingDelete() progress.ApplyingDelete(original, patch1); Assert.Equal(1, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying delete ghi/jkl/!SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying delete ghi/jkl/!SOME_NODE to abc/def.cfg/SOME_NODE"); progress.ApplyingDelete(original, patch2); Assert.Equal(2, progress.Counter.patchedNodes); - logger.Received().Log(LogType.Log, "Applying delete pqr/stu/!SOME_NODE to abc/def.cfg/SOME_NODE"); + logger.AssertInfo("Applying delete pqr/stu/!SOME_NODE to abc/def.cfg/SOME_NODE"); } [Fact] @@ -131,11 +131,11 @@ public void TestNeedsUnsatisfiedRoot() progress.NeedsUnsatisfiedRoot(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedRoot(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its NEEDS"); } [Fact] @@ -148,11 +148,11 @@ public void TestNeedsUnsatisfiedNode() progress.NeedsUnsatisfiedNode(config1, "SOME/NODE/PATH/SOME_CHILD_NODE"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting node in file abc/def subnode: SOME/NODE/PATH/SOME_CHILD_NODE as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting node in file abc/def subnode: SOME/NODE/PATH/SOME_CHILD_NODE as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedNode(config2, "SOME/NODE/PATH/SOME_OTHER_CHILD_NODE"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting node in file ghi/jkl subnode: SOME/NODE/PATH/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting node in file ghi/jkl subnode: SOME/NODE/PATH/SOME_OTHER_CHILD_NODE as it can't satisfy its NEEDS"); } [Fact] @@ -165,11 +165,11 @@ public void TestNeedsUnsatisfiedValue() progress.NeedsUnsatisfiedValue(config1, "SOME/NODE/PATH/some_value"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting value in file abc/def value: SOME/NODE/PATH/some_value as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting value in file abc/def value: SOME/NODE/PATH/some_value as it can't satisfy its NEEDS"); progress.NeedsUnsatisfiedValue(config2, "SOME/NODE/PATH/some_other_value"); Assert.Equal(0, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting value in file ghi/jkl value: SOME/NODE/PATH/some_other_value as it can't satisfy its NEEDS"); + logger.AssertInfo("Deleting value in file ghi/jkl value: SOME/NODE/PATH/some_other_value as it can't satisfy its NEEDS"); } [Fact] @@ -182,11 +182,11 @@ public void TestNeedsUnsatisfiedBefore() progress.NeedsUnsatisfiedBefore(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE"); + logger.AssertInfo("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its BEFORE"); progress.NeedsUnsatisfiedBefore(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE"); + logger.AssertInfo("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its BEFORE"); } [Fact] @@ -199,11 +199,11 @@ public void TestNeedsUnsatisfiedFor() progress.NeedsUnsatisfiedFor(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Warning, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)"); + logger.AssertWarning("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its FOR (this shouldn't happen)"); progress.NeedsUnsatisfiedFor(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Warning, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)"); + logger.AssertWarning("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its FOR (this shouldn't happen)"); } [Fact] @@ -216,11 +216,11 @@ public void TestNeedsUnsatisfiedAfter() progress.NeedsUnsatisfiedAfter(config1); Assert.Equal(1, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER"); + logger.AssertInfo("Deleting root node in file abc/def node: SOME_NODE as it can't satisfy its AFTER"); progress.NeedsUnsatisfiedAfter(config2); Assert.Equal(2, progress.Counter.needsUnsatisfied); - logger.Received().Log(LogType.Log, "Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); + logger.AssertInfo("Deleting root node in file ghi/jkl node: SOME_OTHER_NODE as it can't satisfy its AFTER"); } [Fact] @@ -233,7 +233,7 @@ public void TestStartingPass() progress.PassStarted(pass1); - logger.Received().Log(LogType.Log, ":SOME_PASS pass"); + logger.AssertInfo(":SOME_PASS pass"); onEvent.Received()(pass1); } @@ -250,7 +250,7 @@ public void TestStartingPass__NullArgument() Assert.Equal("pass", ex.ParamName); - logger.DidNotReceiveWithAnyArgs().Log(LogType.Log, null); + logger.AssertNoLog(); onEvent.DidNotReceiveWithAnyArgs()(null); } @@ -265,12 +265,12 @@ public void TestWarning() progress.Warning(config1, "I'm warning you"); Assert.Equal(1, progress.Counter.warnings); Assert.Equal(1, progress.Counter.warningFiles["abc/def.cfg"]); - logger.Received().Log(LogType.Warning, "I'm warning you"); + logger.AssertWarning("I'm warning you"); progress.Warning(config2, "You should probably pay attention to this"); Assert.Equal(2, progress.Counter.warnings); Assert.Equal(2, progress.Counter.warningFiles["abc/def.cfg"]); - logger.Received().Log(LogType.Warning, "You should probably pay attention to this"); + logger.AssertWarning("You should probably pay attention to this"); } [Fact] @@ -297,12 +297,12 @@ public void TestError__Config() progress.Error(config1, "An error message no one is going to read"); Assert.Equal(1, progress.Counter.errors); Assert.Equal(1, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Log(LogType.Error, "An error message no one is going to read"); + logger.AssertError("An error message no one is going to read"); progress.Error(config2, "Maybe someone will read this one"); Assert.Equal(2, progress.Counter.errors); Assert.Equal(2, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Log(LogType.Error, "Maybe someone will read this one"); + logger.AssertError("Maybe someone will read this one"); } [Fact] @@ -315,11 +315,11 @@ public void TestException() progress.Exception("An exception was thrown", e1); Assert.Equal(1, progress.Counter.exceptions); - logger.Received().Exception("An exception was thrown", e1); + logger.AssertException("An exception was thrown", e1); progress.Exception("An exception was tossed", e2); Assert.Equal(2, progress.Counter.exceptions); - logger.Received().Exception("An exception was tossed", e2); + logger.AssertException("An exception was tossed", e2); } [Fact] @@ -336,12 +336,12 @@ public void TestException__Url() progress.Exception(config1, "An exception was thrown", e1); Assert.Equal(1, progress.Counter.exceptions); Assert.Equal(1, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Exception("An exception was thrown", e1); + logger.AssertException("An exception was thrown", e1); progress.Exception(config2, "An exception was tossed", e2); Assert.Equal(2, progress.Counter.exceptions); Assert.Equal(2, progress.Counter.errorFiles["abc/def.cfg"]); - logger.Received().Exception("An exception was tossed", e2); + logger.AssertException("An exception was tossed", e2); } [Fact] From fc74f1fcda46031eb9b3139c7a56830b6728d01d Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 00:32:39 -0700 Subject: [PATCH 282/342] Initialize LogMessage from old LogMessage with new message only uses LogType from old message for now but more coming --- ModuleManager/Logging/LogMessage.cs | 7 ++++ ModuleManager/Logging/ModLogger.cs | 2 +- ModuleManagerTests/Logging/LogMessageTest.cs | 34 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs index 53f3c0f8..82c5a0fe 100644 --- a/ModuleManager/Logging/LogMessage.cs +++ b/ModuleManager/Logging/LogMessage.cs @@ -16,6 +16,13 @@ public LogMessage(LogType logType, string message) Message = message ?? throw new ArgumentNullException(nameof(message)); } + public LogMessage(ILogMessage logMessage, string newMessage) + { + if (logMessage == null) throw new ArgumentNullException(nameof(logMessage)); + LogType = logMessage.LogType; + Message = newMessage ?? throw new ArgumentNullException(nameof(newMessage)); + } + public string ToLogString() { string prefix; diff --git a/ModuleManager/Logging/ModLogger.cs b/ModuleManager/Logging/ModLogger.cs index 89a05066..6fead909 100644 --- a/ModuleManager/Logging/ModLogger.cs +++ b/ModuleManager/Logging/ModLogger.cs @@ -17,7 +17,7 @@ public ModLogger(string prefix, IBasicLogger logger) public void Log(ILogMessage message) { if (message == null) throw new ArgumentNullException(nameof(message)); - logger.Log(new LogMessage(message.LogType, prefix + message.Message)); + logger.Log(new LogMessage(message, prefix + message.Message)); } } } diff --git a/ModuleManagerTests/Logging/LogMessageTest.cs b/ModuleManagerTests/Logging/LogMessageTest.cs index 0a0d42c3..f44c8276 100644 --- a/ModuleManagerTests/Logging/LogMessageTest.cs +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -1,5 +1,6 @@ using System; using Xunit; +using NSubstitute; using UnityEngine; using ModuleManager.Logging; @@ -18,6 +19,39 @@ public void TestConstructor__NullMessage() Assert.Equal("message", ex.ParamName); } + [Fact] + public void TestConstructor__FromOtherMessage() + { + ILogMessage logMessage = Substitute.For(); + logMessage.LogType.Returns(LogType.Log); + logMessage.Message.Returns("the old message"); + LogMessage newLogMessage = new LogMessage(logMessage, "a new message"); + Assert.Equal(LogType.Log, newLogMessage.LogType); + Assert.Equal("a new message", newLogMessage.Message); + } + + [Fact] + public void TestConstructor__FromOtherMessage__LogMessageNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogMessage(null, "a new message"); + }); + + Assert.Equal("logMessage", ex.ParamName); + } + + [Fact] + public void TestConstructor__FromOtherMessage__NewMessageNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + new LogMessage(Substitute.For(), null); + }); + + Assert.Equal("newMessage", ex.ParamName); + } + [Fact] public void TestToLogMessage__Info() { From 07afe29d79f4260bbc6a3e6f517535ebc6afbba9 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 00:53:04 -0700 Subject: [PATCH 283/342] Don't put date on every log message Instead put it once at the top of the log --- ModuleManager/Logging/LogMessage.cs | 1 + ModuleManager/MMPatchLoader.cs | 1 + ModuleManager/MMPatchRunner.cs | 1 + ModuleManagerTests/Logging/LogMessageTest.cs | 12 ++++++------ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs index 82c5a0fe..86348df3 100644 --- a/ModuleManager/Logging/LogMessage.cs +++ b/ModuleManager/Logging/LogMessage.cs @@ -6,6 +6,7 @@ namespace ModuleManager.Logging public class LogMessage : ILogMessage { private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; + private const string DATETIME_FORMAT_STRING = "HH:mm:ss.fff"; public LogType LogType { get; } public string Message { get; } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 4ab1827a..56909ece 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -91,6 +91,7 @@ public IEnumerable Run() { using (StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create))) { + streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); logRunner.Run(streamLogger); streamLogger.Info("Done!"); } diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 802b76fb..739ecd82 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -40,6 +40,7 @@ public IEnumerator Run() { using (StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create))) { + streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); logRunner.Run(streamLogger); streamLogger.Info("Done!"); } diff --git a/ModuleManagerTests/Logging/LogMessageTest.cs b/ModuleManagerTests/Logging/LogMessageTest.cs index f44c8276..6e7d78ff 100644 --- a/ModuleManagerTests/Logging/LogMessageTest.cs +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -56,42 +56,42 @@ public void TestConstructor__FromOtherMessage__NewMessageNull() public void TestToLogMessage__Info() { LogMessage message = new LogMessage(LogType.Log, "everything is ok"); - Assert.Matches(@"^\[LOG \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] everything is ok$", message.ToLogString()); + Assert.Matches(@"^\[LOG \d\d:\d\d:\d\d.\d\d\d\] everything is ok$", message.ToLogString()); } [Fact] public void TestToLogMessage__Warning() { LogMessage message = new LogMessage(LogType.Warning, "I'm warning you"); - Assert.Matches(@"^\[WRN \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] I'm warning you$", message.ToLogString()); + Assert.Matches(@"^\[WRN \d\d:\d\d:\d\d.\d\d\d\] I'm warning you$", message.ToLogString()); } [Fact] public void TestToLogMessage__Error() { LogMessage message = new LogMessage(LogType.Error, "You went too far"); - Assert.Matches(@"^\[ERR \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + Assert.Matches(@"^\[ERR \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); } [Fact] public void TestToLogMessage__Exception() { LogMessage message = new LogMessage(LogType.Exception, "You went too far"); - Assert.Matches(@"^\[EXC \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + Assert.Matches(@"^\[EXC \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); } [Fact] public void TestToLogMessage__Assert() { LogMessage message = new LogMessage(LogType.Assert, "You went too far"); - Assert.Matches(@"^\[AST \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + Assert.Matches(@"^\[AST \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); } [Fact] public void TestToLogMessage__Unknown() { LogMessage message = new LogMessage((LogType)9999, "You went too far"); - Assert.Matches(@"^\[\?\?\? \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); + Assert.Matches(@"^\[\?\?\? \d\d:\d\d:\d\d.\d\d\d\] You went too far$", message.ToLogString()); } [Fact] From 0a2c842ae3b141416b4a176f0fdb5fc1df438760 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 01:03:16 -0700 Subject: [PATCH 284/342] Initialize timestamp with log message So if it takes some time to actually get to the log it will display the originating timestamp rather than the timestamp it was logged at --- ModuleManager/Logging/ILogMessage.cs | 1 + ModuleManager/Logging/LogMessage.cs | 6 ++++-- ModuleManagerTests/Logging/LogMessageTest.cs | 22 ++++++++++++++++++++ ModuleManagerTests/Logging/ModLoggerTest.cs | 21 ++++++++++--------- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/ModuleManager/Logging/ILogMessage.cs b/ModuleManager/Logging/ILogMessage.cs index 55137cc7..bf85f488 100644 --- a/ModuleManager/Logging/ILogMessage.cs +++ b/ModuleManager/Logging/ILogMessage.cs @@ -6,6 +6,7 @@ namespace ModuleManager.Logging public interface ILogMessage { LogType LogType { get; } + DateTime Timestamp { get; } string Message { get; } string ToLogString(); } diff --git a/ModuleManager/Logging/LogMessage.cs b/ModuleManager/Logging/LogMessage.cs index 86348df3..c6554aaf 100644 --- a/ModuleManager/Logging/LogMessage.cs +++ b/ModuleManager/Logging/LogMessage.cs @@ -5,15 +5,16 @@ namespace ModuleManager.Logging { public class LogMessage : ILogMessage { - private const string DATETIME_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss.fff"; private const string DATETIME_FORMAT_STRING = "HH:mm:ss.fff"; public LogType LogType { get; } + public DateTime Timestamp { get; } public string Message { get; } public LogMessage(LogType logType, string message) { LogType = logType; + Timestamp = DateTime.Now; Message = message ?? throw new ArgumentNullException(nameof(message)); } @@ -21,6 +22,7 @@ public LogMessage(ILogMessage logMessage, string newMessage) { if (logMessage == null) throw new ArgumentNullException(nameof(logMessage)); LogType = logMessage.LogType; + Timestamp = logMessage.Timestamp; Message = newMessage ?? throw new ArgumentNullException(nameof(newMessage)); } @@ -40,7 +42,7 @@ public string ToLogString() else prefix = "???"; - return $"[{prefix} {DateTime.Now.ToString(DATETIME_FORMAT_STRING)}] {Message}"; + return $"[{prefix} {Timestamp.ToString(DATETIME_FORMAT_STRING)}] {Message}"; } public override string ToString() diff --git a/ModuleManagerTests/Logging/LogMessageTest.cs b/ModuleManagerTests/Logging/LogMessageTest.cs index 6e7d78ff..d70fe8db 100644 --- a/ModuleManagerTests/Logging/LogMessageTest.cs +++ b/ModuleManagerTests/Logging/LogMessageTest.cs @@ -8,6 +8,16 @@ namespace ModuleManagerTests.Logging { public class LogMessageTest { + [Fact] + public void TestConstructor() + { + LogMessage logMessage = new LogMessage(LogType.Log, "a message"); + Assert.Equal(LogType.Log, logMessage.LogType); + Assert.True(logMessage.Timestamp <= DateTime.Now); + Assert.True(logMessage.Timestamp > DateTime.Now - new TimeSpan(0, 0, 5)); + Assert.Equal("a message", logMessage.Message); + } + [Fact] public void TestConstructor__NullMessage() { @@ -25,8 +35,10 @@ public void TestConstructor__FromOtherMessage() ILogMessage logMessage = Substitute.For(); logMessage.LogType.Returns(LogType.Log); logMessage.Message.Returns("the old message"); + logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 45, 678)); LogMessage newLogMessage = new LogMessage(logMessage, "a new message"); Assert.Equal(LogType.Log, newLogMessage.LogType); + Assert.Equal(logMessage.Timestamp, newLogMessage.Timestamp); Assert.Equal("a new message", newLogMessage.Message); } @@ -100,5 +112,15 @@ public void TestToString() LogMessage message = new LogMessage(LogType.Log, "everything is ok"); Assert.Equal("[ModuleManager.Logging.LogMessage LogType=Log Message=everything is ok]", message.ToString()); } + + [Fact] + public void TestToLogMessage__Timestamp() + { + ILogMessage logMessage = Substitute.For(); + logMessage.LogType.Returns(LogType.Log); + logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 56, 789)); + LogMessage message = new LogMessage(logMessage, "everything is ok"); + Assert.Equal("[LOG 12:34:56.789] everything is ok", message.ToLogString()); + } } } diff --git a/ModuleManagerTests/Logging/ModLoggerTest.cs b/ModuleManagerTests/Logging/ModLoggerTest.cs index 0ca45679..fa15d6be 100644 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ b/ModuleManagerTests/Logging/ModLoggerTest.cs @@ -1,7 +1,7 @@ using System; using Xunit; using NSubstitute; -using ModuleManager.Extensions; +using UnityEngine; using ModuleManager.Logging; namespace ModuleManagerTests.Logging @@ -53,17 +53,18 @@ public void TestConstructor__LoggerNull() [Fact] public void TestLog() { - logger.Info("well hi there"); + ILogMessage logMessage = Substitute.For(); + logMessage.LogType.Returns(LogType.Log); + logMessage.Message.Returns("well hi there"); + logMessage.Timestamp.Returns(new DateTime(2000, 1, 1, 12, 34, 45, 678)); - innerLogger.AssertInfo("[MyMod] well hi there"); - } - - [Fact] - public void TestLog__Warning() - { - logger.Warning("I'm warning you"); + logger.Log(logMessage); - innerLogger.AssertWarning("[MyMod] I'm warning you"); + innerLogger.Received().Log(Arg.Is(msg => + msg.LogType == LogType.Log && + msg.Timestamp == logMessage.Timestamp && + msg.Message == "[MyMod] well hi there" + )); } [Fact] From a8bcad2adbb10413dd8b3175a0b61ef44390533c Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 30 Apr 2019 22:00:25 -0700 Subject: [PATCH 285/342] ModLogger -> PrefixLogger Explains what it does better --- .../Logging/{ModLogger.cs => PrefixLogger.cs} | 4 ++-- ModuleManager/ModuleManager.cs | 4 ++-- ModuleManager/ModuleManager.csproj | 2 +- ModuleManager/PostPatchLoader.cs | 2 +- .../{ModLoggerTest.cs => PrefixLoggerTest.cs} | 14 +++++++------- ModuleManagerTests/ModuleManagerTests.csproj | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) rename ModuleManager/Logging/{ModLogger.cs => PrefixLogger.cs} (85%) rename ModuleManagerTests/Logging/{ModLoggerTest.cs => PrefixLoggerTest.cs} (85%) diff --git a/ModuleManager/Logging/ModLogger.cs b/ModuleManager/Logging/PrefixLogger.cs similarity index 85% rename from ModuleManager/Logging/ModLogger.cs rename to ModuleManager/Logging/PrefixLogger.cs index 6fead909..410473fa 100644 --- a/ModuleManager/Logging/ModLogger.cs +++ b/ModuleManager/Logging/PrefixLogger.cs @@ -2,12 +2,12 @@ namespace ModuleManager.Logging { - public class ModLogger : IBasicLogger + public class PrefixLogger : IBasicLogger { private readonly string prefix; private readonly IBasicLogger logger; - public ModLogger(string prefix, IBasicLogger logger) + public PrefixLogger(string prefix, IBasicLogger logger) { if (string.IsNullOrEmpty(prefix)) throw new ArgumentNullException(nameof(prefix)); this.prefix = $"[{prefix}] "; diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 4d7e76de..50072d91 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -132,7 +132,7 @@ internal void Awake() Log(string.Format("Adding post patch to the loading screen {0}", list.Count)); list.Insert(gameDatabaseIndex + 1, aGameObject.AddComponent()); - patchRunner = new MMPatchRunner(new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); + patchRunner = new MMPatchRunner(new PrefixLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); StartCoroutine(patchRunner.Run()); // Workaround for 1.6.0 Editor bug after a PartDatabase rebuild. @@ -314,7 +314,7 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; - patchRunner = new MMPatchRunner(new ModLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); + patchRunner = new MMPatchRunner(new PrefixLogger("ModuleManager", new UnityLogger(Debug.unityLogger))); float totalLoadWeight = GameDatabase.Instance.LoadWeight() + PartLoader.Instance.LoadWeight(); bool startedReload = false; diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 1a2d77f5..6739e50d 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -51,7 +51,7 @@ - + diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index 758ab0ae..f92f1fad 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -22,7 +22,7 @@ public class PostPatchLoader : LoadingSystem private static readonly List postPatchCallbacks = new List(); - private readonly IBasicLogger logger = new ModLogger("ModuleManager", new UnityLogger(UnityEngine.Debug.unityLogger)); + private readonly IBasicLogger logger = new PrefixLogger("ModuleManager", new UnityLogger(UnityEngine.Debug.unityLogger)); private bool ready = false; diff --git a/ModuleManagerTests/Logging/ModLoggerTest.cs b/ModuleManagerTests/Logging/PrefixLoggerTest.cs similarity index 85% rename from ModuleManagerTests/Logging/ModLoggerTest.cs rename to ModuleManagerTests/Logging/PrefixLoggerTest.cs index fa15d6be..28a82edc 100644 --- a/ModuleManagerTests/Logging/ModLoggerTest.cs +++ b/ModuleManagerTests/Logging/PrefixLoggerTest.cs @@ -6,15 +6,15 @@ namespace ModuleManagerTests.Logging { - public class ModLoggerTest + public class PrefixLoggerTest { private IBasicLogger innerLogger; - private ModLogger logger; + private PrefixLogger logger; - public ModLoggerTest() + public PrefixLoggerTest() { innerLogger = Substitute.For(); - logger = new ModLogger("MyMod", innerLogger); + logger = new PrefixLogger("MyMod", innerLogger); } [Fact] @@ -22,7 +22,7 @@ public void TestConstructor__PrefixNull() { ArgumentNullException e = Assert.Throws(delegate { - new ModLogger(null, innerLogger); + new PrefixLogger(null, innerLogger); }); Assert.Equal("prefix", e.ParamName); @@ -33,7 +33,7 @@ public void TestConstructor__PrefixBlank() { ArgumentNullException e = Assert.Throws(delegate { - new ModLogger("", innerLogger); + new PrefixLogger("", innerLogger); }); Assert.Equal("prefix", e.ParamName); @@ -44,7 +44,7 @@ public void TestConstructor__LoggerNull() { ArgumentNullException e = Assert.Throws(delegate { - new ModLogger("blah", null); + new PrefixLogger("blah", null); }); Assert.Equal("logger", e.ParamName); diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 12379fd6..17d1b48d 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -116,7 +116,7 @@ - + From 5711994c101b86891f9403a5c08c47492665810d Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 13:55:42 -0700 Subject: [PATCH 286/342] Unnecessary using --- ModuleManager/Progress/PatchProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index b1d92721..a1d53ed3 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -1,7 +1,6 @@ using System; using ModuleManager.Extensions; using ModuleManager.Logging; -using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManager.Progress { From 3650d6927de303a7be7b27fdf4d0a144ee496737 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 13:58:08 -0700 Subject: [PATCH 287/342] Use better assertion --- ModuleManagerTests/Patches/PatchCompilerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManagerTests/Patches/PatchCompilerTest.cs b/ModuleManagerTests/Patches/PatchCompilerTest.cs index 5ad3c794..bb786dc5 100644 --- a/ModuleManagerTests/Patches/PatchCompilerTest.cs +++ b/ModuleManagerTests/Patches/PatchCompilerTest.cs @@ -44,7 +44,7 @@ public void TestCompilePatch__Insert() patch.Apply(configs, progress, logger); - Assert.Equal(1, configs.Count); + Assert.Single(configs); Assert.NotSame(protoPatch.urlConfig.config, configs.First.Value.Node); AssertNodesEqual(new TestConfigNode("NODE") { From 5f7071d88369e69beb0e2fc5a74d1a8d3305c159 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 14:06:28 -0700 Subject: [PATCH 288/342] Mark disposables as sealed Microsoft seems to recommend this --- ModuleManager/Collections/MessageQueue.cs | 2 +- ModuleManager/Logging/StreamLogger.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/Collections/MessageQueue.cs b/ModuleManager/Collections/MessageQueue.cs index 321a3503..b6038a77 100644 --- a/ModuleManager/Collections/MessageQueue.cs +++ b/ModuleManager/Collections/MessageQueue.cs @@ -12,7 +12,7 @@ public interface IMessageQueue : IEnumerable public class MessageQueue : IMessageQueue, IEnumerable { - public class Enumerator : IEnumerator + public sealed class Enumerator : IEnumerator { private readonly MessageQueue queue; private Node current; diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index bfbb1053..74115d3e 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -3,7 +3,7 @@ namespace ModuleManager.Logging { - public class StreamLogger : IBasicLogger, IDisposable + public sealed class StreamLogger : IBasicLogger, IDisposable { private readonly Stream stream; private readonly StreamWriter streamWriter; From 28bd925fbe75663c34b67d0bad6c012e01135602 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 14:06:37 -0700 Subject: [PATCH 289/342] Another unnecessary using --- ModuleManager/ModListGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManager/ModListGenerator.cs b/ModuleManager/ModListGenerator.cs index 97292ba0..5d017232 100644 --- a/ModuleManager/ModListGenerator.cs +++ b/ModuleManager/ModListGenerator.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; From 5e5314e2d73e4ec237684793ec8975c2a2bc84ca Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 22:29:35 -0700 Subject: [PATCH 290/342] Use custom code to lookup file by url Should be less gc-happy and potentially faster, though this step rarely takes any signicant amount of time. Allows extensions to be specified optionally. Doesn't support /Parts or /Internals but probably a bunch of assumptions in ModuleManager about that too. --- ModuleManager/Extensions/UrlDirExtensions.cs | 43 +++++++++ ModuleManager/MMPatchLoader.cs | 2 +- ModuleManager/ModuleManager.csproj | 1 + .../Extensions/UrlDirExtensionsTest.cs | 88 +++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 ModuleManager/Extensions/UrlDirExtensions.cs create mode 100644 ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs diff --git a/ModuleManager/Extensions/UrlDirExtensions.cs b/ModuleManager/Extensions/UrlDirExtensions.cs new file mode 100644 index 00000000..3a57bf6a --- /dev/null +++ b/ModuleManager/Extensions/UrlDirExtensions.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; + +namespace ModuleManager.Extensions +{ + public static class UrlDirExtensions + { + public static UrlDir.UrlFile Find(this UrlDir urlDir, string url) + { + if (urlDir == null) throw new ArgumentNullException(nameof(urlDir)); + if (url == null) throw new ArgumentNullException(nameof(url)); + string[] splits = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + UrlDir currentDir = urlDir; + + for (int i = 0; i < splits.Length - 1; i++) + { + currentDir = currentDir.children.FirstOrDefault(subDir => subDir.name == splits[i]); + if (currentDir == null) return null; + } + + string fileName = splits[splits.Length - 1]; + string fileExtension = null; + + int idx = fileName.LastIndexOf('.'); + + if (idx > -1) + { + fileExtension = fileName.Substring(idx + 1); + fileName = fileName.Substring(0, idx); + } + + foreach (UrlDir.UrlFile file in currentDir.files) + { + if (file.name != fileName) continue; + if (fileExtension != null && fileExtension != file.fileExtension) continue; + return file; + } + + return null; + } + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 56909ece..fffd5e3a 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -524,7 +524,7 @@ private IEnumerable LoadCache() { string parentUrl = node.GetValue("parentUrl"); - UrlDir.UrlFile parent = GameDatabase.Instance.root.AllConfigFiles.FirstOrDefault(f => f.url == parentUrl); + UrlDir.UrlFile parent = gameDataDir.Find(parentUrl); if (parent != null) { databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 6739e50d..c69f36b6 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -31,6 +31,7 @@ False + diff --git a/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs new file mode 100644 index 00000000..d7ff2316 --- /dev/null +++ b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs @@ -0,0 +1,88 @@ +using System; +using Xunit; +using TestUtils; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class UrlDirExtensionsTest + { + + [Fact] + public void TestFind__IndirectChild() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def/ghi.cfg", urlDir); + + Assert.Equal(urlFile, urlDir.Find("def/ghi")); + } + + [Fact] + public void TestFind__DirectChild() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def.cfg", urlDir); + + Assert.Equal(urlFile, urlDir.Find("def")); + } + + [Fact] + public void TestFind__Extension() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlBuilder.CreateFile("def/ghi.yyy", urlDir); + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def/ghi.cfg", urlDir); + UrlBuilder.CreateFile("def/ghi.zzz", urlDir); + + Assert.Equal(urlFile, urlDir.Find("def/ghi.cfg")); + } + + [Fact] + public void TestFind__NotFound() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlBuilder.CreateDir("def", urlDir); + + Assert.Null(urlDir.Find("def/ghi")); + } + + [Fact] + public void TestFind__Extension__NotFound() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlBuilder.CreateFile("def/ghi.yyy", urlDir); + UrlBuilder.CreateFile("def/ghi.zzz", urlDir); + + Assert.Null(urlDir.Find("def/ghi.cfg")); + } + + [Fact] + public void TestFind__IntermediateDirectoryNotFound() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + Assert.Null(urlDir.Find("def/ghi")); + } + + [Fact] + public void TestFind__UrlDirNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + UrlDirExtensions.Find(null, "abc"); + }); + + Assert.Equal("urlDir", ex.ParamName); + } + + [Fact] + public void TestFind__UrlNull() + { + ArgumentNullException ex = Assert.Throws(delegate + { + UrlDirExtensions.Find(UrlBuilder.CreateDir("abc"), null); + }); + + Assert.Equal("url", ex.ParamName); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 17d1b48d..7e97430e 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -78,6 +78,7 @@ + From ae0d7a7c50df234e141909eedb2ceb6c5de6f3d2 Mon Sep 17 00:00:00 2001 From: blowfish Date: Sun, 5 May 2019 22:48:22 -0700 Subject: [PATCH 291/342] Put extension on cache URLs Makes trailing spaces not break things Resolves #121 --- ModuleManager/Extensions/UrlFileExtensions.cs | 12 +++++++ ModuleManager/MMPatchLoader.cs | 32 +++++++++++-------- ModuleManager/ModuleManager.cs | 5 +-- ModuleManager/ModuleManager.csproj | 1 + ModuleManager/Progress/PatchProgress.cs | 4 +-- .../Extensions/UrlFileExtensionsTest.cs | 17 ++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 7 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 ModuleManager/Extensions/UrlFileExtensions.cs create mode 100644 ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs diff --git a/ModuleManager/Extensions/UrlFileExtensions.cs b/ModuleManager/Extensions/UrlFileExtensions.cs new file mode 100644 index 00000000..6fe1cbe9 --- /dev/null +++ b/ModuleManager/Extensions/UrlFileExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace ModuleManager.Extensions +{ + public static class UrlFileExtensions + { + public static string GetUrlWithExtension(this UrlDir.UrlFile urlFile) + { + return $"{urlFile.url}.{urlFile.fileExtension}"; + } + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index fffd5e3a..b7e28179 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -292,8 +292,9 @@ private bool IsCacheUpToDate() for (int i = 0; i < files.Length; i++) { + string url = files[i].GetUrlWithExtension(); // Hash the file path so the checksum change if files are moved - byte[] pathBytes = Encoding.UTF8.GetBytes(files[i].url); + byte[] pathBytes = Encoding.UTF8.GetBytes(url); sha.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); // hash the file content @@ -301,13 +302,13 @@ private bool IsCacheUpToDate() sha.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); filesha.ComputeHash(contentBytes); - if (!filesSha.ContainsKey(files[i].url)) + if (!filesSha.ContainsKey(url)) { - filesSha.Add(files[i].url, BitConverter.ToString(filesha.Hash)); + filesSha.Add(url, BitConverter.ToString(filesha.Hash)); } else { - logger.Warning("Duplicate fileSha key. This should not append. The key is " + files[i].url); + logger.Warning("Duplicate fileSha key. This should not append. The key is " + url); } } @@ -370,13 +371,14 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) for (int i = 0; i < files.Length; i++) { - ConfigNode fileNode = GetFileNode(shaConfigNode, files[i].url); + string url = files[i].GetUrlWithExtension(); + ConfigNode fileNode = GetFileNode(shaConfigNode, url); string fileSha = fileNode?.GetValue("SHA"); if (fileNode == null) continue; - if (fileSha == null || filesSha[files[i].url] != fileSha) + if (fileSha == null || filesSha[url] != fileSha) { changes.Append("Changed : " + fileNode.GetValue("filename") + ".cfg\n"); noChange = false; @@ -384,18 +386,19 @@ private bool CheckFilesChange(UrlDir.UrlFile[] files, ConfigNode shaConfigNode) } for (int i = 0; i < files.Length; i++) { - ConfigNode fileNode = GetFileNode(shaConfigNode, files[i].url); + string url = files[i].GetUrlWithExtension(); + ConfigNode fileNode = GetFileNode(shaConfigNode, url); if (fileNode == null) { - changes.Append("Added : " + files[i].url + ".cfg\n"); + changes.Append("Added : " + url + "\n"); noChange = false; } shaConfigNode.RemoveNode(fileNode); } foreach (ConfigNode fileNode in shaConfigNode.GetNodes()) { - changes.Append("Deleted : " + fileNode.GetValue("filename") + ".cfg\n"); + changes.Append("Deleted : " + fileNode.GetValue("filename") + "\n"); noChange = false; } if (!noChange) @@ -430,19 +433,20 @@ private void CreateCache(IEnumerable databaseConfigs, int patch foreach (IProtoUrlConfig urlConfig in databaseConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); - node.AddValue("parentUrl", urlConfig.UrlFile.url); + node.AddValue("parentUrl", urlConfig.UrlFile.GetUrlWithExtension()); node.AddNode(urlConfig.Node); } foreach (var file in GameDatabase.Instance.root.AllConfigFiles) { + string url = file.GetUrlWithExtension(); // "/Physics" is the node we created manually to loads the PHYSIC config - if (file.url != "/Physics" && filesSha.ContainsKey(file.url)) + if (file.url != "/Physics" && filesSha.ContainsKey(url)) { ConfigNode shaNode = filesSHANode.AddNode("FILE"); - shaNode.AddValue("filename", file.url); - shaNode.AddValue("SHA", filesSha[file.url]); - filesSha.Remove(file.url); + shaNode.AddValue("filename", url); + shaNode.AddValue("SHA", filesSha[url]); + filesSha.Remove(url); } } diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 50072d91..75b4f6ec 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -9,6 +9,7 @@ using UnityEngine; using Debug = UnityEngine.Debug; using ModuleManager.Cats; +using ModuleManager.Extensions; using ModuleManager.Logging; namespace ModuleManager @@ -472,8 +473,8 @@ public static void OutputAllConfigs() Directory.CreateDirectory(dirPath); } - Log("Exporting " + currentPath + urlFile.name + "." + urlFile.fileExtension); - string filePath = dirPath + urlFile.name + "." + urlFile.fileExtension; + Log("Exporting " + currentPath + urlFile.GetUrlWithExtension()); + string filePath = dirPath + urlFile.GetUrlWithExtension(); foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) { try diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index c69f36b6..3c4b0366 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -32,6 +32,7 @@ + diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index a1d53ed3..3cebf994 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -139,7 +139,7 @@ public void Exception(UrlDir.UrlConfig url, string message, Exception exception) private void RecordWarningFile(UrlDir.UrlConfig url) { - string key = url.parent.url + "." + url.parent.fileExtension; + string key = url.parent.GetUrlWithExtension(); if (key[0] == '/') key = key.Substring(1); @@ -151,7 +151,7 @@ private void RecordWarningFile(UrlDir.UrlConfig url) private void RecordErrorFile(UrlDir.UrlConfig url) { - string key = url.parent.url + "." + url.parent.fileExtension; + string key = url.parent.GetUrlWithExtension(); if (key[0] == '/') key = key.Substring(1); diff --git a/ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs new file mode 100644 index 00000000..147e603e --- /dev/null +++ b/ModuleManagerTests/Extensions/UrlFileExtensionsTest.cs @@ -0,0 +1,17 @@ +using System; +using Xunit; +using TestUtils; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public static class UrlFileExtensionsTest + { + [Fact] + public static void TestGetUrlWithExtension() + { + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("abc/def/ghi.cfg"); + Assert.Equal("abc/def/ghi.cfg", urlFile.GetUrlWithExtension()); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7e97430e..37a2edaf 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -79,6 +79,7 @@ + From 69ab085da68e4ed3c80f444f0a04bfa65e6171f5 Mon Sep 17 00:00:00 2001 From: blowfish Date: Mon, 27 May 2019 14:35:04 -0700 Subject: [PATCH 292/342] Alphabetize .csproj files --- ModuleManager/ModuleManager.csproj | 22 +++++++-------- ModuleManagerTests/ModuleManagerTests.csproj | 28 ++++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 3c4b0366..ac7665b4 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -31,9 +31,6 @@ False - - - @@ -41,6 +38,7 @@ + @@ -48,22 +46,24 @@ + + - + + + - - + - @@ -73,7 +73,11 @@ + + + + @@ -83,10 +87,6 @@ - - - - diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 37a2edaf..49e52d41 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -75,7 +75,15 @@ + + + + + + + + @@ -84,25 +92,25 @@ - - - + + + + + + - - - @@ -111,14 +119,6 @@ - - - - - - - - From e1bed9419ca20a15e04ba24e8bfe7e69563259e4 Mon Sep 17 00:00:00 2001 From: blowfish Date: Tue, 28 May 2019 21:58:27 -0700 Subject: [PATCH 293/342] improve file sha generator * Actually dispose the sha when we're done * Use a better way of converting bytes to hex strings (hopefully less gc) * Test byte array to hex string conversion --- .../Extensions/ByteArrayExtensions.cs | 29 +++++++++++++++++++ ModuleManager/ModuleManager.csproj | 1 + ModuleManager/Utils/FileUtils.cs | 19 +++++------- .../Extensions/ByteArrayExtensionsTest.cs | 28 ++++++++++++++++++ ModuleManagerTests/ModuleManagerTests.csproj | 1 + 5 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 ModuleManager/Extensions/ByteArrayExtensions.cs create mode 100644 ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs diff --git a/ModuleManager/Extensions/ByteArrayExtensions.cs b/ModuleManager/Extensions/ByteArrayExtensions.cs new file mode 100644 index 00000000..520393df --- /dev/null +++ b/ModuleManager/Extensions/ByteArrayExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace ModuleManager.Extensions +{ + public static class ByteArrayExtensions + { + public static string ToHex(this byte[] data) + { + if (data == null) throw new ArgumentNullException(nameof(data)); + char[] result = new char[data.Length * 2]; + + for (int i = 0; i < data.Length; i++) + { + result[i * 2] = GetHexValue(data[i] / 16); + result[i * 2 + 1] = GetHexValue(data[i] % 16); + } + + return new string(result); + } + + private static char GetHexValue(int i) + { + if (i < 10) + return (char)(i + '0'); + else + return (char)(i - 10 + 'a'); + } + } +} diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index ac7665b4..04a043ae 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -41,6 +41,7 @@ + diff --git a/ModuleManager/Utils/FileUtils.cs b/ModuleManager/Utils/FileUtils.cs index 73cd8dba..e069e14c 100644 --- a/ModuleManager/Utils/FileUtils.cs +++ b/ModuleManager/Utils/FileUtils.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Security.Cryptography; +using ModuleManager.Extensions; namespace ModuleManager.Utils { @@ -9,22 +11,17 @@ public static string FileSHA(string filename) { if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); - System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); - byte[] data = null; - using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) - { - data = sha.ComputeHash(fs); - } - - string hashedValue = string.Empty; - foreach (byte b in data) + using (SHA256 sha = SHA256.Create()) { - hashedValue += String.Format("{0,2:x2}", b); + using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) + { + data = sha.ComputeHash(fs); + } } - return hashedValue; + return data.ToHex(); } } } diff --git a/ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs b/ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs new file mode 100644 index 00000000..ee53dcc8 --- /dev/null +++ b/ModuleManagerTests/Extensions/ByteArrayExtensionsTest.cs @@ -0,0 +1,28 @@ +using System; +using Xunit; +using ModuleManager.Extensions; + +namespace ModuleManagerTests.Extensions +{ + public class ByteArrayExtensionsTest + { + [Fact] + public void TestToHex() + { + byte[] data = { 0x00, 0xff, 0x01, 0xfe, 0x02, 0xfd, 0x9a }; + + Assert.Equal("00ff01fe02fd9a", data.ToHex()); + } + + [Fact] + public void TestToHex__NullData() + { + ArgumentNullException ex = Assert.Throws(delegate + { + ByteArrayExtensions.ToHex(null); + }); + + Assert.Equal("data", ex.ParamName); + } + } +} diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 49e52d41..9824ace1 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -81,6 +81,7 @@ + From a27cb3df58b5b2218cfff2b0027959ce7332864f Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 30 May 2019 22:18:20 -0700 Subject: [PATCH 294/342] Convert UrlBuilder cfg test to theory allows more inline data to be added --- TestUtils/UrlBuilder.cs | 1 + TestUtilsTests/UrlBuilderTest.cs | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/TestUtils/UrlBuilder.cs b/TestUtils/UrlBuilder.cs index 2fff4a47..9e7f1393 100644 --- a/TestUtils/UrlBuilder.cs +++ b/TestUtils/UrlBuilder.cs @@ -118,6 +118,7 @@ public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) bool cfg = false; string newName = name; + // KSP tries to load .cfg files so need to have special handling if (extension == "cfg") { cfg = true; diff --git a/TestUtilsTests/UrlBuilderTest.cs b/TestUtilsTests/UrlBuilderTest.cs index 956350dd..8a685779 100644 --- a/TestUtilsTests/UrlBuilderTest.cs +++ b/TestUtilsTests/UrlBuilderTest.cs @@ -209,23 +209,22 @@ public void TestCreateFile__Parent() Assert.Contains(file, root.AllFiles); } - // KSP tries to load .cfg files so need to have special handling - [Fact] - public void TestCreateFile__cfg() + [InlineData("cfg", UrlDir.FileType.Config)] + [Theory] + public void TestCreateFile__Extension(string extension, UrlDir.FileType fileType) { - UrlDir root = UrlBuilder.CreateRoot(); - UrlDir dir = UrlBuilder.CreateDir("someDir", root); - UrlDir.UrlFile file = UrlBuilder.CreateFile("someFile.cfg", dir); + UrlDir.UrlFile file = UrlBuilder.CreateFile("someFile." + extension); Assert.Equal("someFile", file.name); Assert.Equal("cfg", file.fileExtension); - Assert.Equal(UrlDir.FileType.Config, file.fileType); - Assert.Same(dir, file.parent); - Assert.Same(root, file.root); + Assert.Equal(fileType, file.fileType); - Assert.Equal("someDir/someFile", file.url); - Assert.Contains(file, dir.files); - Assert.Contains(file, root.AllConfigFiles); + UrlDir root = file.parent; + Assert.NotNull(root); + Assert.Equal("root", root.name); + Assert.Null(root.parent); + Assert.Contains(file, root.files); + Assert.Same(root, file.root); } [Fact] From 3d8594dd855febd0024a0ac876ad7e8ce03d2848 Mon Sep 17 00:00:00 2001 From: blowfish Date: Thu, 30 May 2019 22:37:47 -0700 Subject: [PATCH 295/342] Set FileType based on common types Makes checking against them easier --- TestUtils/UrlBuilder.cs | 35 +++++++++++++++++++++++++++++++- TestUtilsTests/UrlBuilderTest.cs | 17 +++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/TestUtils/UrlBuilder.cs b/TestUtils/UrlBuilder.cs index 9e7f1393..e8e60bd5 100644 --- a/TestUtils/UrlBuilder.cs +++ b/TestUtils/UrlBuilder.cs @@ -118,6 +118,38 @@ public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) bool cfg = false; string newName = name; + UrlDir.FileType fileType = UrlDir.FileType.Unknown; + + switch (extension) + { + case "dll": + fileType = UrlDir.FileType.Assembly; + break; + case "ksp": + fileType = UrlDir.FileType.AssetBundle; + break; + case "wav": + case "ogg": + fileType = UrlDir.FileType.Audio; + break; + case "cfg": + fileType = UrlDir.FileType.Config; + break; + case "dae": + case "mu": + fileType = UrlDir.FileType.Model; + break; + case "dds": + case "jpg": + case "jpeg": + case "mbm": + case "png": + case "tga": + case "truecolor": + fileType = UrlDir.FileType.Texture; + break; + } + // KSP tries to load .cfg files so need to have special handling if (extension == "cfg") { @@ -127,11 +159,12 @@ public static UrlDir.UrlFile CreateFile(string path, UrlDir parent = null) UrlDir.UrlFile file = new UrlDir.UrlFile(parent, new FileInfo(newName)); + UrlFile__field__fileType.SetValue(file, fileType); + if (cfg) { UrlFile__field__name.SetValue(file, nameWithoutExtension); UrlFile__field__fileExtension.SetValue(file, "cfg"); - UrlFile__field__fileType.SetValue(file, UrlDir.FileType.Config); } parent.files.Add(file); diff --git a/TestUtilsTests/UrlBuilderTest.cs b/TestUtilsTests/UrlBuilderTest.cs index 8a685779..38a788f0 100644 --- a/TestUtilsTests/UrlBuilderTest.cs +++ b/TestUtilsTests/UrlBuilderTest.cs @@ -209,14 +209,29 @@ public void TestCreateFile__Parent() Assert.Contains(file, root.AllFiles); } + [InlineData("dll", UrlDir.FileType.Assembly)] + [InlineData("ksp", UrlDir.FileType.AssetBundle)] + [InlineData("wav", UrlDir.FileType.Audio)] + [InlineData("ogg", UrlDir.FileType.Audio)] [InlineData("cfg", UrlDir.FileType.Config)] + [InlineData("dae", UrlDir.FileType.Model)] + [InlineData("mu", UrlDir.FileType.Model)] + [InlineData("dds", UrlDir.FileType.Texture)] + [InlineData("jpg", UrlDir.FileType.Texture)] + [InlineData("jpeg", UrlDir.FileType.Texture)] + [InlineData("mbm", UrlDir.FileType.Texture)] + [InlineData("png", UrlDir.FileType.Texture)] + [InlineData("tga", UrlDir.FileType.Texture)] + [InlineData("truecolor", UrlDir.FileType.Texture)] + [InlineData("txt", UrlDir.FileType.Unknown)] + [InlineData("xml", UrlDir.FileType.Unknown)] [Theory] public void TestCreateFile__Extension(string extension, UrlDir.FileType fileType) { UrlDir.UrlFile file = UrlBuilder.CreateFile("someFile." + extension); Assert.Equal("someFile", file.name); - Assert.Equal("cfg", file.fileExtension); + Assert.Equal(extension, file.fileExtension); Assert.Equal(fileType, file.fileType); UrlDir root = file.parent; From eb3db605b24f56af6e4da5a17a8dbf3b9795292e Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 31 May 2019 00:33:33 -0700 Subject: [PATCH 296/342] unnecessary using --- ModuleManagerTests/Logging/StreamLoggerTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index 61535387..734a3521 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using UnityEngine; using Xunit; using NSubstitute; using ModuleManager.Logging; From 9795e3656b9e73a7ed1622d2b832002ad9b70d38 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 31 May 2019 00:40:53 -0700 Subject: [PATCH 297/342] make StreamLoggerTest.TestLog work on \n platforms line breaks are shorter which causes an extra null character to exist at the end of the string, meaning that the default Trim() wasn't removing that and the newline --- ModuleManagerTests/Logging/StreamLoggerTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index 734a3521..bc613038 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -69,7 +69,7 @@ public void TestLog() { using (StreamReader reader = new StreamReader(stream)) { - string result = reader.ReadToEnd().Trim(); + string result = reader.ReadToEnd().Trim('\r', '\n', '\0'); Assert.Equal("[OMG wtf] bbq", result); } } From 9311cfb45507323f3f56f315dbda61f5034a31b0 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 22:55:57 -0700 Subject: [PATCH 298/342] Improve TestConfigNodeTest Strictly assert the structure of the node --- TestUtilsTests/TestConfigNodeTest.cs | 45 +++++++++++++++++----------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/TestUtilsTests/TestConfigNodeTest.cs b/TestUtilsTests/TestConfigNodeTest.cs index 6b00b4de..0858ece6 100644 --- a/TestUtilsTests/TestConfigNodeTest.cs +++ b/TestUtilsTests/TestConfigNodeTest.cs @@ -34,31 +34,40 @@ public void TestTestConfigNode() }, }; - Assert.Equal("something", node.GetValue("value1")); - Assert.Equal("something else", node.GetValue("value2")); - Assert.Equal(new[] { "first", "second" }, node.GetValues("multiple")); - Assert.Equal("bar", node.GetValue("foo")); + Assert.Equal(5, node.values.Count); + AssertValue("value1", "something", node.values[0]); + AssertValue("value2", "something else", node.values[1]); + AssertValue("multiple", "first", node.values[2]); + AssertValue("multiple", "second", node.values[3]); + AssertValue("foo", "bar", node.values[4]); + Assert.Equal(3, node.nodes.Count); ConfigNode innerNode1 = node.GetNode("NODE_1"); Assert.NotNull(innerNode1); - Assert.Equal("NODE_1", innerNode1.name); - Assert.Equal("something", innerNode1.GetValue("name")); - Assert.Equal("something else", innerNode1.GetValue("stuff")); + Assert.Equal("NODE_1", node.nodes[0].name); + Assert.Equal(2, node.nodes[0].values.Count); + AssertValue("name", "something", node.nodes[0].values[0]); + AssertValue("stuff", "something else", node.nodes[0].values[1]); + Assert.Empty(node.nodes[0].nodes); - ConfigNode[] innerNodes2 = node.GetNodes("MULTIPLE"); - Assert.NotNull(innerNodes2); - Assert.Equal(2, innerNodes2.Length); + Assert.Equal("MULTIPLE", node.nodes[1].name); + Assert.Equal(2, node.nodes[1].values.Count); + AssertValue("value3", "blah", node.nodes[1].values[0]); + AssertValue("value4", "bleh", node.nodes[1].values[1]); + Assert.Empty(node.nodes[1].nodes); - ConfigNode innerNode2a = innerNodes2[0]; - Assert.NotNull(innerNode2a); - Assert.Equal("blah", innerNode2a.GetValue("value3")); - Assert.Equal("bleh", innerNode2a.GetValue("value4")); + Assert.Equal("MULTIPLE", node.nodes[2].name); + Assert.Equal(2, node.nodes[2].values.Count); + AssertValue("value3", "blih", node.nodes[2].values[0]); + AssertValue("value4", "bloh", node.nodes[2].values[1]); + Assert.Empty(node.nodes[2].nodes); + } - ConfigNode innerNode2b = innerNodes2[1]; - Assert.NotNull(innerNode2b); - Assert.Equal("blih", innerNode2b.GetValue("value3")); - Assert.Equal("bloh", innerNode2b.GetValue("value4")); + private void AssertValue(string name, string value, ConfigNode.Value nodeValue) + { + Assert.Equal(name, nodeValue.name); + Assert.Equal(value, nodeValue.value); } } } From ee5995f6196cfe58f2f646b1f8f8f847b32b9f0d Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 22:58:26 -0700 Subject: [PATCH 299/342] Remove redundant parent class --- TestUtils/TestConfigNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TestUtils/TestConfigNode.cs b/TestUtils/TestConfigNode.cs index a954c107..6b71a3ae 100644 --- a/TestUtils/TestConfigNode.cs +++ b/TestUtils/TestConfigNode.cs @@ -9,7 +9,7 @@ public TestConfigNode() : base() { } public TestConfigNode(string name) : base(name) { } public void Add(string name, string value) => AddValue(name, value); - public void Add(ConfigNode.Value value) => values.Add(value); + public void Add(Value value) => values.Add(value); public void Add(string name, ConfigNode node) => AddNode(name, node); public void Add(ConfigNode node) => AddNode(node); From 3e4a9f998ecd40118e07bce7c655da6fb15a8442 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 23:03:27 -0700 Subject: [PATCH 300/342] Fix TestConfigNode's handling of escaped chars KSP removes these automatically, so we want to avoid that by constructing the Value ourselves --- TestUtils/TestConfigNode.cs | 2 +- TestUtilsTests/TestConfigNodeTest.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TestUtils/TestConfigNode.cs b/TestUtils/TestConfigNode.cs index 6b71a3ae..694fc55e 100644 --- a/TestUtils/TestConfigNode.cs +++ b/TestUtils/TestConfigNode.cs @@ -8,7 +8,7 @@ public class TestConfigNode : ConfigNode, IEnumerable public TestConfigNode() : base() { } public TestConfigNode(string name) : base(name) { } - public void Add(string name, string value) => AddValue(name, value); + public void Add(string name, string value) => Add(new Value(name, value)); public void Add(Value value) => values.Add(value); public void Add(string name, ConfigNode node) => AddNode(name, node); public void Add(ConfigNode node) => AddNode(node); diff --git a/TestUtilsTests/TestConfigNodeTest.cs b/TestUtilsTests/TestConfigNodeTest.cs index 0858ece6..e544da78 100644 --- a/TestUtilsTests/TestConfigNodeTest.cs +++ b/TestUtilsTests/TestConfigNodeTest.cs @@ -16,6 +16,7 @@ public void TestTestConfigNode() { "multiple", "first" }, { "multiple", "second" }, new ConfigNode.Value("foo", "bar"), + { "weird_values", "some\r\n\tstuff" }, { "NODE_1", new TestConfigNode { { "name", "something" }, @@ -34,12 +35,13 @@ public void TestTestConfigNode() }, }; - Assert.Equal(5, node.values.Count); + Assert.Equal(6, node.values.Count); AssertValue("value1", "something", node.values[0]); AssertValue("value2", "something else", node.values[1]); AssertValue("multiple", "first", node.values[2]); AssertValue("multiple", "second", node.values[3]); AssertValue("foo", "bar", node.values[4]); + AssertValue("weird_values", "some\r\n\tstuff", node.values[5]); Assert.Equal(3, node.nodes.Count); ConfigNode innerNode1 = node.GetNode("NODE_1"); From 8e5c29ad0983157c88116085d4745e125941a416 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 23:18:01 -0700 Subject: [PATCH 301/342] Clean up test a bit Use AssertValue method Use AssertEmpty rather than asserting count zero --- .../Extensions/ConfigNodeExtensionsTest.cs | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index 7e4030ad..fcf9533e 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -49,13 +49,11 @@ public void TestShallowCopyFrom() Assert.Same(value1, fromNode.values[0]); Assert.Same(value1, toNode.values[0]); - Assert.Equal("abc", value1.name); - Assert.Equal("def", value1.value); + AssertValue("abc", "def", value1); Assert.Same(value2, fromNode.values[1]); Assert.Same(value2, toNode.values[1]); - Assert.Equal("ghi", value2.name); - Assert.Equal("jkl", value2.value); + AssertValue("ghi", "jkl", value2); Assert.Equal(2, fromNode.nodes.Count); Assert.Equal(2, toNode.nodes.Count); @@ -64,23 +62,21 @@ public void TestShallowCopyFrom() Assert.Same(innerNode1, toNode.nodes[0]); Assert.Equal("INNER_NODE_1", innerNode1.name); Assert.Equal(1, innerNode1.values.Count); - Assert.Equal("mno", innerNode1.values[0].name); - Assert.Equal("pqr", innerNode1.values[0].value); + AssertValue("mno", "pqr", innerNode1.values[0]); Assert.Equal(1, innerNode1.nodes.Count); Assert.Equal("INNER_INNER_NODE_1", innerNode1.nodes[0].name); - Assert.Equal(0, innerNode1.nodes[0].values.Count); - Assert.Equal(0, innerNode1.nodes[0].nodes.Count); + Assert.Empty(innerNode1.nodes[0].values); + Assert.Empty(innerNode1.nodes[0].nodes); Assert.Same(innerNode2, fromNode.nodes[1]); Assert.Same(innerNode2, toNode.nodes[1]); Assert.Equal("INNER_NODE_2", innerNode2.name); Assert.Equal(1, innerNode2.values.Count); - Assert.Equal("stu", innerNode2.values[0].name); - Assert.Equal("vwx", innerNode2.values[0].value); + AssertValue("stu", "vwx", innerNode2.values[0]); Assert.Equal(1, innerNode2.nodes.Count); Assert.Equal("INNER_INNER_NODE_2", innerNode2.nodes[0].name); - Assert.Equal(0, innerNode2.nodes[0].values.Count); - Assert.Equal(0, innerNode2.nodes[0].nodes.Count); + Assert.Empty(innerNode2.nodes[0].values); + Assert.Empty(innerNode2.nodes[0].nodes); } [Fact] @@ -109,13 +105,11 @@ public void TestDeepCopy() Assert.Equal(2, toNode.values.Count); Assert.NotSame(fromNode.values[0], toNode.values[0]); - Assert.Equal("abc", toNode.values[0].name); - Assert.Equal("def", toNode.values[0].value); - + AssertValue("abc", "def", toNode.values[0]); + Assert.NotSame(fromNode.values[1], toNode.values[1]); - Assert.Equal("ghi", toNode.values[1].name); - Assert.Equal("jkl", toNode.values[1].value); - + AssertValue("ghi", "jkl", toNode.values[1]); + Assert.Equal(2, toNode.nodes.Count); ConfigNode innerNode1 = toNode.nodes[0]; @@ -123,26 +117,24 @@ public void TestDeepCopy() Assert.Equal("INNER_NODE_1", innerNode1.name); Assert.Equal(1, innerNode1.values.Count); Assert.NotSame(fromNode.nodes[0].values[0], innerNode1.values[0]); - Assert.Equal("mno", innerNode1.values[0].name); - Assert.Equal("pqr", innerNode1.values[0].value); + AssertValue("mno", "pqr", innerNode1.values[0]); Assert.Equal(1, toNode.nodes[0].nodes.Count); Assert.NotSame(fromNode.nodes[0].nodes[0], innerNode1.nodes[0]); Assert.Equal("INNER_INNER_NODE_1", innerNode1.nodes[0].name); - Assert.Equal(0, innerNode1.nodes[0].values.Count); - Assert.Equal(0, innerNode1.nodes[0].nodes.Count); + Assert.Empty(innerNode1.nodes[0].values); + Assert.Empty(innerNode1.nodes[0].nodes); ConfigNode innerNode2 = toNode.nodes[1]; Assert.NotSame(fromNode.nodes[1], innerNode2); Assert.Equal("INNER_NODE_2", innerNode2.name); Assert.Equal(1, innerNode2.values.Count); Assert.NotSame(fromNode.nodes[1].values[0], innerNode2.values[0]); - Assert.Equal("stu", innerNode2.values[0].name); - Assert.Equal("vwx", innerNode2.values[0].value); + AssertValue("stu", "vwx", innerNode2.values[0]); Assert.Equal(1, innerNode2.nodes.Count); Assert.NotSame(fromNode.nodes[1].nodes[0], innerNode2.nodes[0]); Assert.Equal("INNER_INNER_NODE_2", innerNode2.nodes[0].name); - Assert.Equal(0, innerNode2.nodes[0].values.Count); - Assert.Equal(0, innerNode2.nodes[0].nodes.Count); + Assert.Empty(innerNode2.nodes[0].values); + Assert.Empty(innerNode2.nodes[0].nodes); } [Fact] @@ -273,5 +265,11 @@ XX INNER_NODE node.PrettyPrint(ref sb, "XX"); Assert.Equal(expected, sb.ToString()); } + + private void AssertValue(string name, string value, ConfigNode.Value nodeValue) + { + Assert.Equal(name, nodeValue.name); + Assert.Equal(value, nodeValue.value); + } } } From 0feef88557d938ee16b889ba5685db307be2c798 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 26 Jun 2019 23:26:15 -0700 Subject: [PATCH 302/342] Fix DeepCopy handling of escaped values KSP removes these in AddValue, we can circumvent that by constructing the Value ourselves --- ModuleManager/Extensions/ConfigNodeExtensions.cs | 2 +- ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 6940048a..79c192b5 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -19,7 +19,7 @@ public static ConfigNode DeepCopy(this ConfigNode from) { ConfigNode to = new ConfigNode(from.name); foreach (ConfigNode.Value value in from.values) - to.AddValue(value.name, value.value); + to.values.Add(new ConfigNode.Value(value.name, value.value)); foreach (ConfigNode node in from.nodes) { ConfigNode newNode = DeepCopy(node); diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index fcf9533e..d771bf0d 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -89,6 +89,7 @@ public void TestDeepCopy() new TestConfigNode("INNER_NODE_1") { { "mno", "pqr" }, + { "weird_values", "some\r\n\tstuff" }, new TestConfigNode("INNER_INNER_NODE_1"), }, new TestConfigNode("INNER_NODE_2") @@ -115,9 +116,11 @@ public void TestDeepCopy() ConfigNode innerNode1 = toNode.nodes[0]; Assert.NotSame(fromNode.nodes[0], innerNode1); Assert.Equal("INNER_NODE_1", innerNode1.name); - Assert.Equal(1, innerNode1.values.Count); + Assert.Equal(2, innerNode1.values.Count); Assert.NotSame(fromNode.nodes[0].values[0], innerNode1.values[0]); AssertValue("mno", "pqr", innerNode1.values[0]); + Assert.NotSame(fromNode.nodes[0].values[1], innerNode1.values[1]); + AssertValue("weird_values", "some\r\n\tstuff", innerNode1.values[1]); Assert.Equal(1, toNode.nodes[0].nodes.Count); Assert.NotSame(fromNode.nodes[0].nodes[0], innerNode1.nodes[0]); Assert.Equal("INNER_INNER_NODE_1", innerNode1.nodes[0].name); From 96201a72d6c1037c95dc7f98da8177c25ee867f0 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 27 Jun 2019 00:23:57 -0700 Subject: [PATCH 303/342] Fix ModifyNode handling of escaped characters new AddValueSafe method to handle this use safe ShallowCopyFrom rather than ConfigNode's CopyTo as recursion is not necessary and it handles escaped characters correctly --- .../Extensions/ConfigNodeExtensions.cs | 7 +- ModuleManager/MMPatchLoader.cs | 26 +++---- .../Extensions/ConfigNodeExtensionsTest.cs | 15 ++++ ModuleManagerTests/MMPatchLoaderTest.cs | 78 +++++++++++++++++++ 4 files changed, 112 insertions(+), 14 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index 79c192b5..a54100ba 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -19,7 +19,7 @@ public static ConfigNode DeepCopy(this ConfigNode from) { ConfigNode to = new ConfigNode(from.name); foreach (ConfigNode.Value value in from.values) - to.values.Add(new ConfigNode.Value(value.name, value.value)); + to.AddValueSafe(value.name, value.value); foreach (ConfigNode node in from.nodes) { ConfigNode newNode = DeepCopy(node); @@ -68,5 +68,10 @@ public static void PrettyPrint(this ConfigNode node, ref StringBuilder sb, strin sb.AppendFormat("{0}}}\n", indent); } + + public static void AddValueSafe(this ConfigNode node, string name, string value) + { + node.values.Add(new ConfigNode.Value(name, value)); + } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index b7e28179..367805db 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -725,7 +725,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (varValue != null) { newNode.RemoveValues(valName); - newNode.AddValue(valName, varValue); + newNode.AddValueSafe(valName, varValue); } else { @@ -771,7 +771,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (cmd != Command.Copy) origVal.value = value; else - newNode.AddValue(valName, value); + newNode.AddValueSafe(valName, value); } } else @@ -847,7 +847,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon if (varValue != null) { if (!newNode.HasValue(valName)) - newNode.AddValue(valName, varValue); + newNode.AddValueSafe(valName, varValue); } else { @@ -1010,8 +1010,8 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon msg += " Applying subnode " + subMod.name + "\n"; #endif ConfigNode newSubNode = ModifyNode(nodeStack.Push(subNodes[0]), subMod, context); - subNodes[0].ClearData(); - newSubNode.CopyTo(subNodes[0], newSubNode.name); + subNodes[0].ShallowCopyFrom(newSubNode); + subNodes[0].name = newSubNode.name; } else { @@ -1023,7 +1023,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon ConfigNode copy = new ConfigNode(nodeType); if (nodeName != null) - copy.AddValue("name", nodeName); + copy.AddValueSafe("name", nodeName); ConfigNode newSubNode = ModifyNode(nodeStack.Push(copy), subMod, context); newNode.nodes.Add(newSubNode); @@ -1040,7 +1040,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon ConfigNode copy = new ConfigNode(nodeType); if (nodeName != null) - copy.AddValue("name", nodeName); + copy.AddValueSafe("name", nodeName); ConfigNode newSubNode = ModifyNode(nodeStack.Push(copy), subMod, context); newNode.nodes.Add(newSubNode); @@ -1066,8 +1066,8 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon // Edit in place newSubNode = ModifyNode(nodeStack.Push(subNode), subMod, context); - subNode.ClearData(); - newSubNode.CopyTo(subNode, newSubNode.name); + subNode.ShallowCopyFrom(newSubNode); + subNode.name = newSubNode.name; break; case Command.Delete: @@ -1698,13 +1698,13 @@ private static void InsertValue(ConfigNode newNode, int index, string name, stri newNode.RemoveValues(name); int i = 0; for (; i < index; ++i) - newNode.AddValue(name, oldValues[i]); - newNode.AddValue(name, value); + newNode.AddValueSafe(name, oldValues[i]); + newNode.AddValueSafe(name, value); for (; i < oldValues.Length; ++i) - newNode.AddValue(name, oldValues[i]); + newNode.AddValueSafe(name, oldValues[i]); return; } - newNode.AddValue(name, value); + newNode.AddValueSafe(name, value); } //FindConfigNodeIn finds and returns a ConfigNode in src of type nodeType. diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index d771bf0d..cb0c5e1f 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -269,6 +269,21 @@ XX INNER_NODE Assert.Equal(expected, sb.ToString()); } + [Fact] + public void TestAddValueSafe() + { + ConfigNode node = new TestConfigNode + { + { "key1", "value1" }, + }; + + node.AddValueSafe("weird_values", "some\r\n\tstuff"); + + Assert.Equal(2, node.values.Count); + AssertValue("key1", "value1", node.values[0]); + AssertValue("weird_values", "some\r\n\tstuff", node.values[1]); + } + private void AssertValue(string name, string value, ConfigNode.Value nodeValue) { Assert.Equal(name, nodeValue.name); diff --git a/ModuleManagerTests/MMPatchLoaderTest.cs b/ModuleManagerTests/MMPatchLoaderTest.cs index 29682448..b0617fe8 100644 --- a/ModuleManagerTests/MMPatchLoaderTest.cs +++ b/ModuleManagerTests/MMPatchLoaderTest.cs @@ -71,6 +71,84 @@ public void TestModifyNode__MultiplyValue() }, c3); } + [Fact] + public void TestModifyNode__EditNode__SpecialCharacters() + { + ConfigNode c1 = new TestConfigNode("NODE") + { + new TestConfigNode("INNER_NODE") + { + { "weird_values", "some\r\n\tstuff" }, + }, + }; + + UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("@NODE") + { + new TestConfigNode("@INNER_NODE") + { + { "another_weird_value", "some\r\nmore\tstuff" }, + }, + }); + + PatchContext context = new PatchContext(c2u, Enumerable.Empty(), logger, progress); + + ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context); + + EnsureNoErrors(); + + AssertConfigNodesEqual(new TestConfigNode("NODE") + { + new TestConfigNode("INNER_NODE") + { + { "weird_values", "some\r\n\tstuff" }, + { "another_weird_value", "some\r\nmore\tstuff" }, + }, + }, c3); + } + + [Fact] + public void TestModifyNode__ReplaceNode__SpecialCharacters() + { + ConfigNode c1 = new TestConfigNode("NODE") + { + new TestConfigNode("INNER_NODE") + { + { "weird_values", "some\r\n\tstuff" }, + }, + }; + + UrlDir.UrlConfig c2u = UrlBuilder.CreateConfig("abc/def", new TestConfigNode("@NODE") + { + new TestConfigNode("%INNER_NODE") + { + { "another_weird_value", "some\r\nmore\tstuff" }, + }, + new TestConfigNode("%OTHER_INNER_NODE") + { + { "another_weirder_value", "even\r\nmore\tstuff" }, + }, + }); + + PatchContext context = new PatchContext(c2u, Enumerable.Empty(), logger, progress); + + ConfigNode c3 = MMPatchLoader.ModifyNode(new NodeStack(c1), c2u.config, context); + + EnsureNoErrors(); + + AssertConfigNodesEqual(new TestConfigNode("NODE") + { + new TestConfigNode("INNER_NODE") + { + { "weird_values", "some\r\n\tstuff" }, + { "another_weird_value", "some\r\nmore\tstuff" }, + }, + new TestConfigNode("OTHER_INNER_NODE") + { + { "another_weirder_value", "even\r\nmore\tstuff" }, + }, + }, c3); + } + private void AssertConfigNodesEqual(ConfigNode expected, ConfigNode observed) { Assert.Equal(expected.ToString(), observed.ToString()); From f2fba86ce98bcba5ab13b7d3ce6c6fd7e51e74ea Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 27 Jun 2019 00:40:17 -0700 Subject: [PATCH 304/342] Ensure tabs and newlines don't break cache Explicitly escape \n and \t which the localizer unescapes when game database is initially loaded --- ModuleManager/MMPatchLoader.cs | 37 +++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 367805db..243aa0bb 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -430,11 +430,30 @@ private void CreateCache(IEnumerable databaseConfigs, int patch cache.AddValue("patchedNodeCount", patchedNodeCount.ToString()); + // Undoes escaping done by the localizer + void FixValuesRecursive(ConfigNode theNode) + { + foreach (ConfigNode subNode in theNode.nodes) + { + FixValuesRecursive(subNode); + } + + foreach (ConfigNode.Value value in theNode.values) + { + value.value = value.value.Replace("\n", "\\n"); + value.value = value.value.Replace("\t", "\\t"); + } + } + foreach (IProtoUrlConfig urlConfig in databaseConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); node.AddValue("parentUrl", urlConfig.UrlFile.GetUrlWithExtension()); - node.AddNode(urlConfig.Node); + + ConfigNode urlNode = urlConfig.Node.DeepCopy(); + FixValuesRecursive(urlNode); + + node.AddNode(urlNode); } foreach (var file in GameDatabase.Instance.root.AllConfigFiles) @@ -524,6 +543,21 @@ private IEnumerable LoadCache() List databaseConfigs = new List(cache.nodes.Count); + // Evaluate escape sequences + void FixValuesRecursive(ConfigNode theNode) + { + foreach (ConfigNode subNode in theNode.nodes) + { + FixValuesRecursive(subNode); + } + + foreach (ConfigNode.Value value in theNode.values) + { + value.value = value.value.Replace("\\n", "\n"); + value.value = value.value.Replace("\\t", "\t"); + } + } + foreach (ConfigNode node in cache.nodes) { string parentUrl = node.GetValue("parentUrl"); @@ -531,6 +565,7 @@ private IEnumerable LoadCache() UrlDir.UrlFile parent = gameDataDir.Find(parentUrl); if (parent != null) { + FixValuesRecursive(node.nodes[0]); databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); } else From 3d85ddca5fcf62e81e21904fcb04364039081bbf Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 7 Aug 2019 01:03:23 +0200 Subject: [PATCH 305/342] v4.0.3 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 43b2dc37..26449b74 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.0.2")] +[assembly: AssemblyVersion("4.0.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From aa6ff2e35e6dc06c7b5ea3f2941d2d621fd90791 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 12 Aug 2019 21:08:53 -0700 Subject: [PATCH 306/342] Extract escaping/unescaping of node values Allows it to be tested --- .../Extensions/ConfigNodeExtensions.cs | 30 ++++++++++++ ModuleManager/MMPatchLoader.cs | 34 +------------- .../Extensions/ConfigNodeExtensionsTest.cs | 46 +++++++++++++++++++ 3 files changed, 78 insertions(+), 32 deletions(-) diff --git a/ModuleManager/Extensions/ConfigNodeExtensions.cs b/ModuleManager/Extensions/ConfigNodeExtensions.cs index a54100ba..9f552566 100644 --- a/ModuleManager/Extensions/ConfigNodeExtensions.cs +++ b/ModuleManager/Extensions/ConfigNodeExtensions.cs @@ -73,5 +73,35 @@ public static void AddValueSafe(this ConfigNode node, string name, string value) { node.values.Add(new ConfigNode.Value(name, value)); } + + public static void EscapeValuesRecursive(this ConfigNode theNode) + { + foreach (ConfigNode subNode in theNode.nodes) + { + subNode.EscapeValuesRecursive(); + } + + foreach (ConfigNode.Value value in theNode.values) + { + value.value = value.value.Replace("\n", "\\n"); + value.value = value.value.Replace("\r", "\\r"); + value.value = value.value.Replace("\t", "\\t"); + } + } + + public static void UnescapeValuesRecursive(this ConfigNode theNode) + { + foreach (ConfigNode subNode in theNode.nodes) + { + subNode.UnescapeValuesRecursive(); + } + + foreach (ConfigNode.Value value in theNode.values) + { + value.value = value.value.Replace("\\n", "\n"); + value.value = value.value.Replace("\\r", "\r"); + value.value = value.value.Replace("\\t", "\t"); + } + } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 243aa0bb..41e28bd3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -430,28 +430,13 @@ private void CreateCache(IEnumerable databaseConfigs, int patch cache.AddValue("patchedNodeCount", patchedNodeCount.ToString()); - // Undoes escaping done by the localizer - void FixValuesRecursive(ConfigNode theNode) - { - foreach (ConfigNode subNode in theNode.nodes) - { - FixValuesRecursive(subNode); - } - - foreach (ConfigNode.Value value in theNode.values) - { - value.value = value.value.Replace("\n", "\\n"); - value.value = value.value.Replace("\t", "\\t"); - } - } - foreach (IProtoUrlConfig urlConfig in databaseConfigs) { ConfigNode node = cache.AddNode("UrlConfig"); node.AddValue("parentUrl", urlConfig.UrlFile.GetUrlWithExtension()); ConfigNode urlNode = urlConfig.Node.DeepCopy(); - FixValuesRecursive(urlNode); + urlNode.EscapeValuesRecursive(); node.AddNode(urlNode); } @@ -543,21 +528,6 @@ private IEnumerable LoadCache() List databaseConfigs = new List(cache.nodes.Count); - // Evaluate escape sequences - void FixValuesRecursive(ConfigNode theNode) - { - foreach (ConfigNode subNode in theNode.nodes) - { - FixValuesRecursive(subNode); - } - - foreach (ConfigNode.Value value in theNode.values) - { - value.value = value.value.Replace("\\n", "\n"); - value.value = value.value.Replace("\\t", "\t"); - } - } - foreach (ConfigNode node in cache.nodes) { string parentUrl = node.GetValue("parentUrl"); @@ -565,7 +535,7 @@ void FixValuesRecursive(ConfigNode theNode) UrlDir.UrlFile parent = gameDataDir.Find(parentUrl); if (parent != null) { - FixValuesRecursive(node.nodes[0]); + node.nodes[0].UnescapeValuesRecursive(); databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); } else diff --git a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs index cb0c5e1f..471be9cb 100644 --- a/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/ConfigNodeExtensionsTest.cs @@ -284,6 +284,52 @@ public void TestAddValueSafe() AssertValue("weird_values", "some\r\n\tstuff", node.values[1]); } + [Fact] + public void TestEscapeValuesRecursive() + { + ConfigNode node = new TestConfigNode + { + { "key1", "value1" }, + { "key2", "value\nwith\rescped\tchars" }, + new TestConfigNode("SUBNODE") + { + { "key3", "value\nwith\rescped\tchars2" }, + }, + }; + + node.EscapeValuesRecursive(); + + Assert.Equal(2, node.values.Count); + AssertValue("key1", "value1", node.values[0]); + AssertValue("key2", "value\\nwith\\rescped\\tchars", node.values[1]); + Assert.Equal(1, node.nodes.Count); + Assert.Equal(1, node.nodes[0].values.Count); + AssertValue("key3", "value\\nwith\\rescped\\tchars2", node.nodes[0].values[0]); + } + + [Fact] + public void TestUnescapeValuesRecursive() + { + ConfigNode node = new TestConfigNode + { + { "key1", "value1" }, + { "key2", "value\\nwith\\rescped\\tchars" }, + new TestConfigNode("SUBNODE") + { + { "key3", "value\\nwith\\rescped\\tchars2" }, + }, + }; + + node.UnescapeValuesRecursive(); + + Assert.Equal(2, node.values.Count); + AssertValue("key1", "value1", node.values[0]); + AssertValue("key2", "value\nwith\rescped\tchars", node.values[1]); + Assert.Equal(1, node.nodes.Count); + Assert.Equal(1, node.nodes[0].values.Count); + AssertValue("key3", "value\nwith\rescped\tchars2", node.nodes[0].values[0]); + } + private void AssertValue(string name, string value, ConfigNode.Value nodeValue) { Assert.Equal(name, nodeValue.name); From 4201d5fd486810d249327e76482a617181a03cc7 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 12 Aug 2019 21:09:47 -0700 Subject: [PATCH 307/342] Fix dump database to files * Fix paths getting duplicated resulting in files not being written * Escape node values before writing --- ModuleManager/Extensions/UrlFileExtensions.cs | 4 ++ ModuleManager/ModuleManager.cs | 65 ++++++++++--------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/ModuleManager/Extensions/UrlFileExtensions.cs b/ModuleManager/Extensions/UrlFileExtensions.cs index 6fe1cbe9..3d203b24 100644 --- a/ModuleManager/Extensions/UrlFileExtensions.cs +++ b/ModuleManager/Extensions/UrlFileExtensions.cs @@ -8,5 +8,9 @@ public static string GetUrlWithExtension(this UrlDir.UrlFile urlFile) { return $"{urlFile.url}.{urlFile.fileExtension}"; } + public static string GetNameWithExtension(this UrlDir.UrlFile urlFile) + { + return $"{urlFile.name}.{urlFile.fileExtension}"; + } } } diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 75b4f6ec..18425f3f 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -430,7 +430,7 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) public static void OutputAllConfigs() { - string path = KSPUtil.ApplicationRootPath + "/_MMCfgOutput/"; + string path = Path.GetFullPath(Path.Combine(KSPUtil.ApplicationRootPath, "_MMCfgOutput")); try { Directory.CreateDirectory(path); @@ -451,51 +451,52 @@ public static void OutputAllConfigs() { Log("Exception while cleaning the export dir\n" + unauthorizedAccessException); } - Stack dirs = new Stack(); - dirs.Push(GameDatabase.Instance.root); - Stack paths = new Stack(); - paths.Push(""); - try + void WriteDirectoryRecursive(UrlDir currentDir, string dirPath) { - while (dirs.Count > 0) + if (currentDir.files.Count > 0) Directory.CreateDirectory(dirPath); + + foreach (UrlDir.UrlFile urlFile in currentDir.files) { - var currentDir = dirs.Pop(); - string currentPath = paths.Pop(); - - foreach (UrlDir.UrlFile urlFile in currentDir.files) + if (urlFile.fileType != UrlDir.FileType.Config) continue; + + Log("Exporting " + urlFile.GetUrlWithExtension()); + string filePath = Path.Combine(dirPath, urlFile.GetNameWithExtension()); + + bool first = true; + + using (FileStream stream = new FileStream(filePath, FileMode.Create)) + using (StreamWriter writer = new StreamWriter(stream)) { - if (urlFile.fileType == UrlDir.FileType.Config) + foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) { - string dirPath = path + currentPath; - if (!Directory.Exists(dirPath)) + try { - Directory.CreateDirectory(dirPath); - } + if (first) first = false; + else writer.Write("\n"); - Log("Exporting " + currentPath + urlFile.GetUrlWithExtension()); - string filePath = dirPath + urlFile.GetUrlWithExtension(); - foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) + ConfigNode copy = urlConfig.config.DeepCopy(); + copy.EscapeValuesRecursive(); + writer.Write(copy.ToString()); + } + catch (Exception e) { - try - { - File.AppendAllText(filePath, urlConfig.config.ToString()); - } - catch (Exception e) - { - Log("Exception while trying to write the file " + filePath + "\n" + e); - } + Log("Exception while trying to write the file " + filePath + "\n" + e); } } } + } - foreach (UrlDir urlDir in currentDir.children) - { - dirs.Push(urlDir); - paths.Push(currentPath + urlDir.name + "/"); - } + foreach (UrlDir urlDir in currentDir.children) + { + WriteDirectoryRecursive(urlDir, Path.Combine(dirPath, urlDir.name)); } } + + try + { + WriteDirectoryRecursive(GameDatabase.Instance.root, path); + } catch (DirectoryNotFoundException directoryNotFoundException) { Log("Exception while exporting the cfg\n" + directoryNotFoundException); From cc11776ad6576ae756ae1c03f1903734a191c735 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 12 Aug 2019 21:31:23 -0700 Subject: [PATCH 308/342] Fix reload dialog showing 100% when it shouldn't PartLoader reports 100% done before it has started loading --- ModuleManager/ModuleManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 18425f3f..b10c977c 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -344,7 +344,7 @@ private IEnumerator DataBaseReloadWithMM(bool dump = false) { progressFraction = 0f; } - else if (!GameDatabase.Instance.IsReady()) + else if (!GameDatabase.Instance.IsReady() || !PostPatchLoader.Instance.IsReady()) { progressFraction = GameDatabase.Instance.ProgressFraction() * GameDatabase.Instance.LoadWeight(); progressFraction /= totalLoadWeight; From a79618e0d1c040ef2ec572e06acc1709a3284716 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 16 Oct 2019 22:37:47 +0200 Subject: [PATCH 309/342] KSP 1.8 ! With less cat bugs --- ModuleManager/Cats/CatManager.cs | 2 +- ModuleManager/Cats/CatMover.cs | 12 ++++++++++- ModuleManager/ModuleManager.cs | 2 +- ModuleManager/ModuleManager.csproj | 34 ++++++++++++++++++++++++------ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/ModuleManager/Cats/CatManager.cs b/ModuleManager/Cats/CatManager.cs index 0567de54..744a0ca8 100644 --- a/ModuleManager/Cats/CatManager.cs +++ b/ModuleManager/Cats/CatManager.cs @@ -94,7 +94,7 @@ private static GameObject LaunchCat(int scale) sr.sprite = catFrames[0]; - trail.material = new Material(Shader.Find("Particles/Alpha Blended")); + trail.material = new Material(Shader.Find("Legacy Shaders/Particles/Alpha Blended")); Debug.Log("material = " + trail.material); trail.material.mainTexture = rainbow; diff --git a/ModuleManager/Cats/CatMover.cs b/ModuleManager/Cats/CatMover.cs index f5dd7678..2e37ac9a 100644 --- a/ModuleManager/Cats/CatMover.cs +++ b/ModuleManager/Cats/CatMover.cs @@ -21,6 +21,8 @@ public class CatMover : MonoBehaviour private const float time = 5; private const float trailTime = time / 4; + private bool clearTrail = false; + // Use this for initialization void Start() { @@ -36,6 +38,7 @@ void Start() totalLenth = (int) (Screen.width / time * trail.time) + 150; trail.time = trailTime; trail.widthCurve = new AnimationCurve(new Keyframe(0, trail.startWidth ), new Keyframe(0.7f, trail.startWidth), new Keyframe(1, trail.startWidth * 0.9f)); + clearTrail = true; } void Update() @@ -50,7 +53,7 @@ void Update() if (activePos > (Screen.width + totalLenth)) { activePos = -spriteRenderer.sprite.rect.width; - trail.time = 0; + clearTrail = true; } float f = 2f * Mathf.PI * (activePos) / (Screen.width * 0.5f); @@ -62,6 +65,13 @@ void Update() transform.position = KSP.UI.UIMainCamera.Camera.ScreenToWorldPoint(spos); transform.rotation = Quaternion.Euler(0, 0, Mathf.Cos(f) * 0.25f * Mathf.PI * Mathf.Rad2Deg); + + if (clearTrail) + { + trail.Clear(); + clearTrail = false; + } + } diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index b10c977c..cfc88979 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -81,7 +81,7 @@ internal void Awake() totalTime.Start(); - // Allow loading the background in the laoding screen + // Allow loading the background in the loading screen Application.runInBackground = true; QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 04a043ae..8110ed4f 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -1,5 +1,5 @@  - + Debug AnyCPU @@ -9,7 +9,8 @@ Library ModuleManager ModuleManager - v3.5 + v4.7 + True @@ -21,6 +22,7 @@ 4 False default + false none @@ -29,6 +31,7 @@ prompt 4 False + false @@ -115,16 +118,35 @@ False False + + False + - + + False - False + + + False + + + False + + + False + + + False + + + False False - C:\Games\KSPSteamController\KSPSteamCtrlr\KSPUnity-Steam-Symlinks\UnityEngine.UI.dll - False + + + False From 5aa82999b419e5c05ab7b8076b3034cb8e861b6d Mon Sep 17 00:00:00 2001 From: Sarbian Date: Wed, 16 Oct 2019 22:38:00 +0200 Subject: [PATCH 310/342] v4.1.0 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 26449b74..c9549df2 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.0.3")] +[assembly: AssemblyVersion("4.1.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ff637237458fbd372ca64a8da4d105ae0c0d8719 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sun, 27 Oct 2019 15:36:21 +0100 Subject: [PATCH 311/342] v4.1.1 - Outdated Firespitter warning --- ModuleManager/ModuleManager.cs | 12 ++++++++++++ ModuleManager/Properties/AssemblyInfo.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index cfc88979..30db5f59 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -184,6 +184,18 @@ private void Start() // //if (GUI.Button(new Rect(Screen.width / 2f - 100, offsetY, 200, 20), "Click to open the Forum thread")) // // Application.OpenURL("http://forum.kerbalspaceprogram.com/index.php?/topic/124998-silent-patch-for-ksp-105-published/"); //} + + if (Versioning.version_major == 1 && Versioning.version_minor >= 8) + { + foreach (AssemblyLoader.LoadedAssembly assembly in AssemblyLoader.loadedAssemblies) + { + AssemblyName assemblyName = assembly.assembly.GetName(); + if (assemblyName.Name == "Firespitter" && assemblyName.Version <= Version.Parse("7.3.7175.38653")) + { + warning.text = "You are using a version of Firespitter that does not run properly on KSP 1.8+\nThis version may prevent the game from loading properly and may create problems for other mods"; + } + } + } } private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index c9549df2..b9e12e75 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.1.0")] +[assembly: AssemblyVersion("4.1.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From d172fc66ff56ffdc665ea47a3a143df8f17f441c Mon Sep 17 00:00:00 2001 From: Sarbian Date: Fri, 15 Nov 2019 22:32:36 +0100 Subject: [PATCH 312/342] Add an Exception interceptor to catch ReflectionTypeLoadException and properly blame broken DLLs --- .../ExceptionIntercept/InterceptLogHandler.cs | 56 +++++++++++++++++++ ModuleManager/ModuleManager.cs | 5 ++ ModuleManager/ModuleManager.csproj | 2 + 3 files changed, 63 insertions(+) create mode 100644 ModuleManager/ExceptionIntercept/InterceptLogHandler.cs diff --git a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs new file mode 100644 index 00000000..a1af8d5c --- /dev/null +++ b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace ModuleManager.UnityLogHandle +{ + class InterceptLogHandler : ILogHandler + { + private readonly ILogHandler baseLogHandler; + private readonly List brokenAssemblies = new List(); + private readonly int gamePathLength; + + public static string Warnings { get; private set; } = ""; + + public InterceptLogHandler() + { + baseLogHandler = Debug.unityLogger.logHandler; + Debug.unityLogger.logHandler = this; + gamePathLength = Path.GetFullPath(KSPUtil.ApplicationRootPath).Length; + } + + public void LogFormat(LogType logType, Object context, string format, params object[] args) + { + baseLogHandler.LogFormat(logType, context, format, args); + } + + public void LogException(Exception exception, Object context) + { + baseLogHandler.LogException(exception, context); + + if (exception is ReflectionTypeLoadException ex) + { + ModuleManager.Log("Intercepted a ReflectionTypeLoadException. List of broken DLLs:"); + var assemblies = ex.Types.Where(x => x != null).Select(x => x.Assembly).Distinct(); + foreach (Assembly assembly in assemblies) + { + if (Warnings == "") + { + Warnings = "ModuleManager mod(s) DLL that are not compatible with this version of KSP\n"; + } + if (!brokenAssemblies.Contains(assembly)) + { + brokenAssemblies.Add(assembly); + Warnings += assembly.GetName().Name + " " + assembly.GetName().Version + " " + assembly.Location.Remove(0, gamePathLength) + "\n"; + } + ModuleManager.Log(assembly.GetName().Name + " " + assembly.GetName().Version + " " + assembly.Location.Remove(0, gamePathLength)); + } + } + baseLogHandler.LogException(exception, context); + } + } +} diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 30db5f59..4b8689ba 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -11,6 +11,7 @@ using ModuleManager.Cats; using ModuleManager.Extensions; using ModuleManager.Logging; +using ModuleManager.UnityLogHandle; namespace ModuleManager { @@ -40,6 +41,8 @@ public class ModuleManager : MonoBehaviour private MMPatchRunner patchRunner; + private InterceptLogHandler interceptLogHandler; + #endregion state private static bool loadedInScene; @@ -196,6 +199,7 @@ private void Start() } } } + interceptLogHandler = new InterceptLogHandler(); } private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) @@ -289,6 +293,7 @@ internal void Update() { if (warning) { + warning.text = InterceptLogHandler.Warnings; h = warning.text.Length > 0 ? warning.textBounds.size.y : 0; offsetY = offsetY + h; warning.rectTransform.localPosition = new Vector3(0, offsetY); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 8110ed4f..d256b235 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -108,6 +108,7 @@ + @@ -194,6 +195,7 @@ + sh -c "TARGET_PATH='$(TargetPath)' TARGET_DIR='$(TargetDir)' TARGET_NAME='$(TargetName)' sh '$(ProjectDir)/copy_build.sh'" From 1adea49112048cec35c3a971042ee5517ae0d416 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Fri, 15 Nov 2019 22:32:50 +0100 Subject: [PATCH 313/342] v4.1.2 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index b9e12e75..58db010d 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.1.1")] +[assembly: AssemblyVersion("4.1.2")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ae4d27ace9ef61603a813d368609c9d7eba4d378 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 30 Nov 2019 15:46:59 +0100 Subject: [PATCH 314/342] Cleanup the InterceptLogHandler, remove double logging and avoid any risk of throwing more --- .../ExceptionIntercept/InterceptLogHandler.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs index a1af8d5c..6cd3dcd0 100644 --- a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs +++ b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs @@ -34,23 +34,32 @@ public void LogException(Exception exception, Object context) if (exception is ReflectionTypeLoadException ex) { - ModuleManager.Log("Intercepted a ReflectionTypeLoadException. List of broken DLLs:"); - var assemblies = ex.Types.Where(x => x != null).Select(x => x.Assembly).Distinct(); - foreach (Assembly assembly in assemblies) + string message = "Intercepted a ReflectionTypeLoadException. List of broken DLLs:\n"; + try { - if (Warnings == "") + var assemblies = ex.Types.Where(x => x != null).Select(x => x.Assembly).Distinct(); + foreach (Assembly assembly in assemblies) { - Warnings = "ModuleManager mod(s) DLL that are not compatible with this version of KSP\n"; + if (Warnings == "") + { + Warnings = "Mod(s) DLL that are not compatible with this version of KSP\n"; + } + string modInfo = assembly.GetName().Name + " " + assembly.GetName().Version + " " + + assembly.Location.Remove(0, gamePathLength) + "\n"; + if (!brokenAssemblies.Contains(assembly)) + { + brokenAssemblies.Add(assembly); + Warnings += modInfo; + } + message += modInfo; } - if (!brokenAssemblies.Contains(assembly)) - { - brokenAssemblies.Add(assembly); - Warnings += assembly.GetName().Name + " " + assembly.GetName().Version + " " + assembly.Location.Remove(0, gamePathLength) + "\n"; - } - ModuleManager.Log(assembly.GetName().Name + " " + assembly.GetName().Version + " " + assembly.Location.Remove(0, gamePathLength)); } + catch (Exception e) + { + message += "Exception " + e.GetType().Name + " while handling the exception..."; + } + ModuleManager.Log(message); } - baseLogHandler.LogException(exception, context); } } } From 51cdc23933193823a45e711174bd4a9591593fd3 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 30 Nov 2019 15:47:17 +0100 Subject: [PATCH 315/342] v4.1.3 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 58db010d..cf5d1a0c 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.1.2")] +[assembly: AssemblyVersion("4.1.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 4b7319c9da475e6d7598723151264b9de7112e06 Mon Sep 17 00:00:00 2001 From: Sarbian Date: Sat, 30 Nov 2019 15:54:06 +0100 Subject: [PATCH 316/342] Start the Handler earlier --- ModuleManager/ModuleManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 4b8689ba..ac452b97 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -83,6 +83,8 @@ internal void Awake() } totalTime.Start(); + + interceptLogHandler = new InterceptLogHandler(); // Allow loading the background in the loading screen Application.runInBackground = true; @@ -199,7 +201,6 @@ private void Start() } } } - interceptLogHandler = new InterceptLogHandler(); } private TextMeshProUGUI CreateTextObject(Canvas canvas, string name) From 16ca42a586661ba24be3086dfa73e75386752081 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 8 Nov 2019 22:20:00 -0800 Subject: [PATCH 317/342] Put both projects on .NET 4.7.1 --- ModuleManager/ModuleManager.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index d256b235..d58d8d14 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -9,7 +9,7 @@ Library ModuleManager ModuleManager - v4.7 + v4.7.1 From 8be22e017db270080393ad6e4cc7e0846b5816d1 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 8 Nov 2019 22:20:11 -0800 Subject: [PATCH 318/342] Add UnityEngine.CoreModule to test project --- ModuleManagerTests/ModuleManagerTests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 9824ace1..b4fafe96 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -60,6 +60,7 @@ + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll True From 35b02dff99eea2d83c6315c264bc46534fa9b9fc Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 8 Nov 2019 22:21:45 -0800 Subject: [PATCH 319/342] Use multi-argument Path.Combine now available in .NET 4 --- ModuleManager/FilePathRepository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index 4ba34321..db15419e 100644 --- a/ModuleManager/FilePathRepository.cs +++ b/ModuleManager/FilePathRepository.cs @@ -5,7 +5,7 @@ namespace ModuleManager { internal static class FilePathRepository { - internal static readonly string cachePath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigCache"); + internal static readonly string cachePath = Path.Combine(KSPUtil.ApplicationRootPath, "GameData", "ModuleManager.ConfigCache"); internal static readonly string techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); internal static readonly string techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); @@ -16,9 +16,9 @@ internal static class FilePathRepository internal static readonly string partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); - internal static readonly string shaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.ConfigSHA"); + internal static readonly string shaPath = Path.Combine(KSPUtil.ApplicationRootPath, "GameData", "ModuleManager.ConfigSHA"); - internal static readonly string logsDirPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "Logs"), "ModuleManager"); + internal static readonly string logsDirPath = Path.Combine(KSPUtil.ApplicationRootPath, "Logs", "ModuleManager"); internal static readonly string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); internal static readonly string patchLogPath = Path.Combine(logsDirPath, "MMPatch.log"); } From 51290e18ab97ab990bffbe3f3886310d500530d8 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Sat, 9 Nov 2019 21:12:39 -0800 Subject: [PATCH 320/342] Adress a bunch of messages Delete unused stuff, make stuff readonly, simplify stuff, add suppressions to legitimate things --- ModuleManager/MMPatchLoader.cs | 20 +++++++------- ModuleManager/ModuleManager.cs | 48 ++++++++++++++-------------------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 41e28bd3..1cffe055 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -40,7 +40,7 @@ public class MMPatchLoader private static readonly KeyValueCache regexCache = new KeyValueCache(); private string configSha; - private Dictionary filesSha = new Dictionary(); + private readonly Dictionary filesSha = new Dictionary(); private const int STATUS_UPDATE_INVERVAL_MS = 33; @@ -89,12 +89,10 @@ public IEnumerable Run() QueueLogRunner logRunner = new QueueLogRunner(patchLogQueue); ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { - using (StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create))) - { - streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); - logRunner.Run(streamLogger); - streamLogger.Info("Done!"); - } + using StreamLogger streamLogger = new StreamLogger(new FileStream(patchLogPath, FileMode.Create)); + streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); + logRunner.Run(streamLogger); + streamLogger.Info("Done!"); }); IBasicLogger patchLogger = new LogSplitter(logger, new QueueLogger(patchLogQueue)); @@ -284,8 +282,8 @@ private bool IsCacheUpToDate() Stopwatch sw = new Stopwatch(); sw.Start(); - System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); - System.Security.Cryptography.SHA256 filesha = System.Security.Cryptography.SHA256.Create(); + using System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); + using System.Security.Cryptography.SHA256 filesha = System.Security.Cryptography.SHA256.Create(); UrlDir.UrlFile[] files = GameDatabase.Instance.root.AllConfigFiles.ToArray(); filesSha.Clear(); @@ -570,7 +568,7 @@ private void StatusUpdate(IPatchProgress progress, string activity = null) #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?"); + private static readonly Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?"); // ModifyNode applies the ConfigNode mod as a 'patch' to ConfigNode original, then returns the patched ConfigNode. // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse. @@ -1401,7 +1399,7 @@ private static string ProcessVariableSearch(string value, NodeStack nodeStack, P StringBuilder builder = new StringBuilder(); builder.Append(split[0].Substring(1)); - for (int i = 1; i < split.Length - 1; i = i + 2) + for (int i = 1; i < split.Length - 1; i += 2) { ConfigNode.Value result = RecurseVariableSearch(split[i], nodeStack, context); if (result == null || result.value == null) diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index ac452b97..361cb540 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -23,12 +24,8 @@ public class ModuleManager : MonoBehaviour private bool inRnDCenter; public bool showUI = false; - - private Rect windowPos = new Rect(80f, 60f, 240f, 40f); private float textPos = 0; - private string version = ""; - //private Texture2D tex; //private Texture2D tex2; @@ -62,7 +59,7 @@ public static void Log(String s) print("[ModuleManager] " + s); } - private Stopwatch totalTime = new Stopwatch(); + private readonly Stopwatch totalTime = new Stopwatch(); internal void Awake() { @@ -107,9 +104,6 @@ internal void Awake() } DontDestroyOnLoad(gameObject); - Version v = Assembly.GetExecutingAssembly().GetName().Version; - version = v.Major + "." + v.Minor + "." + v.Build; - // Subscribe to the RnD center spawn/deSpawn events GameEvents.onGUIRnDComplexSpawn.Add(OnRnDCenterSpawn); GameEvents.onGUIRnDComplexDespawn.Add(OnRnDCenterDeSpawn); @@ -168,7 +162,7 @@ internal void Awake() private TextMeshProUGUI errors; private TextMeshProUGUI warning; - + [SuppressMessage("Code Quality", "IDE0051", Justification = "Called by Unity")] private void Start() { if (nCats) @@ -296,7 +290,7 @@ internal void Update() { warning.text = InterceptLogHandler.Warnings; h = warning.text.Length > 0 ? warning.textBounds.size.y : 0; - offsetY = offsetY + h; + offsetY += h; warning.rectTransform.localPosition = new Vector3(0, offsetY); } @@ -304,7 +298,7 @@ internal void Update() { status.text = patchRunner.Status; h = status.text.Length > 0 ? status.textBounds.size.y : 0; - offsetY = offsetY + h; + offsetY += h; status.transform.localPosition = new Vector3(0, offsetY); } @@ -312,7 +306,7 @@ internal void Update() { errors.text = patchRunner.Errors; h = errors.text.Length > 0 ? errors.textBounds.size.y : 0; - offsetY = offsetY + h; + offsetY += h; errors.transform.localPosition = new Vector3(0, offsetY); } } @@ -470,7 +464,7 @@ public static void OutputAllConfigs() Log("Exception while cleaning the export dir\n" + unauthorizedAccessException); } - void WriteDirectoryRecursive(UrlDir currentDir, string dirPath) + static void WriteDirectoryRecursive(UrlDir currentDir, string dirPath) { if (currentDir.files.Count > 0) Directory.CreateDirectory(dirPath); @@ -483,24 +477,22 @@ void WriteDirectoryRecursive(UrlDir currentDir, string dirPath) bool first = true; - using (FileStream stream = new FileStream(filePath, FileMode.Create)) - using (StreamWriter writer = new StreamWriter(stream)) + using FileStream stream = new FileStream(filePath, FileMode.Create); + using StreamWriter writer = new StreamWriter(stream); + foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) { - foreach (UrlDir.UrlConfig urlConfig in urlFile.configs) + try { - try - { - if (first) first = false; - else writer.Write("\n"); + if (first) first = false; + else writer.Write("\n"); - ConfigNode copy = urlConfig.config.DeepCopy(); - copy.EscapeValuesRecursive(); - writer.Write(copy.ToString()); - } - catch (Exception e) - { - Log("Exception while trying to write the file " + filePath + "\n" + e); - } + ConfigNode copy = urlConfig.config.DeepCopy(); + copy.EscapeValuesRecursive(); + writer.Write(copy.ToString()); + } + catch (Exception e) + { + Log("Exception while trying to write the file " + filePath + "\n" + e); } } } From a2fdef4c38b6aeb066b7f23c212e357eedeacb3d Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Sun, 5 Jul 2020 23:22:43 -0700 Subject: [PATCH 321/342] Don't overwrite other fatal messages --- ModuleManager/MMPatchRunner.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index 739ecd82..b812ceed 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -80,12 +80,14 @@ public IEnumerator Run() { kspLogger.Exception("The patching thread threw an exception", patchingThreadStatus.Exception); FatalErrorHandler.HandleFatalError("The patching thread threw an exception"); + yield break; } if (loggingThreadStatus.IsExitedWithError) { kspLogger.Exception("The logging thread threw an exception", loggingThreadStatus.Exception); FatalErrorHandler.HandleFatalError("The logging thread threw an exception"); + yield break; } if (databaseConfigs == null) From c4ad2c3dd5defcdd37154831be4fa7040d3d1616 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Sun, 5 Jul 2020 23:23:45 -0700 Subject: [PATCH 322/342] Fix :LAST when mod doesn't exist Per the original feature design (#96) it should still run --- ModuleManager/PatchList.cs | 13 +++++++++---- ModuleManagerTests/PatchListTest.cs | 16 +++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index 9716de22..e9193f74 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -76,6 +76,7 @@ public ModPassCollection(IEnumerable modList) private readonly Pass finalPatches = new Pass(":FINAL"); private readonly ModPassCollection modPasses; + private readonly SortedDictionary lastPasses = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); public PatchList(IEnumerable modList, IEnumerable patches, IPatchProgress progress) { @@ -114,8 +115,12 @@ public PatchList(IEnumerable modList, IEnumerable patches, IPatc } else if (patch.PassSpecifier is LastPassSpecifier lastPassSpecifier) { - EnsureMod(lastPassSpecifier.mod); - modPasses[lastPassSpecifier.mod].AddLastPatch(patch); + if (!lastPasses.TryGetValue(lastPassSpecifier.mod, out Pass thisPass)) + { + thisPass = new Pass($":LAST[{lastPassSpecifier.mod.ToUpperInvariant()}]"); + lastPasses.Add(lastPassSpecifier.mod.ToLowerInvariant(), thisPass); + } + thisPass.Add(patch); } else if (patch.PassSpecifier is FinalPassSpecifier) { @@ -143,9 +148,9 @@ public IEnumerator GetEnumerator() yield return modPass.afterPass; } - foreach (ModPass modPass in modPasses) + foreach (Pass lastPass in lastPasses.Values) { - yield return modPass.lastPass; + yield return lastPass; } yield return finalPatches; diff --git a/ModuleManagerTests/PatchListTest.cs b/ModuleManagerTests/PatchListTest.cs index 09b1c1dc..4dc5c751 100644 --- a/ModuleManagerTests/PatchListTest.cs +++ b/ModuleManagerTests/PatchListTest.cs @@ -113,6 +113,7 @@ public void Test__Lifecycle() Substitute.For(), Substitute.For(), Substitute.For(), + Substitute.For(), }; UrlDir.UrlConfig urlConfig = UrlBuilder.CreateConfig("abc/def", new ConfigNode("NODE")); @@ -139,8 +140,9 @@ public void Test__Lifecycle() patches[19].PassSpecifier.Returns(new AfterPassSpecifier("MOD2", urlConfig)); patches[20].PassSpecifier.Returns(new LastPassSpecifier("mod2")); patches[21].PassSpecifier.Returns(new LastPassSpecifier("MOD2")); - patches[22].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[22].PassSpecifier.Returns(new LastPassSpecifier("mod3")); patches[23].PassSpecifier.Returns(new FinalPassSpecifier()); + patches[24].PassSpecifier.Returns(new FinalPassSpecifier()); patches[00].CountsAsPatch.Returns(false); patches[01].CountsAsPatch.Returns(false); @@ -166,6 +168,7 @@ public void Test__Lifecycle() patches[21].CountsAsPatch.Returns(true); patches[22].CountsAsPatch.Returns(true); patches[23].CountsAsPatch.Returns(true); + patches[24].CountsAsPatch.Returns(true); IPatchProgress progress = Substitute.For(); @@ -173,7 +176,7 @@ public void Test__Lifecycle() IPass[] passes = patchList.ToArray(); - Assert.Equal(12, passes.Length); + Assert.Equal(13, passes.Length); Assert.Equal(":INSERT (initial)", passes[0].Name); Assert.Equal(new[] { patches[0], patches[1] }, passes[0]); @@ -208,10 +211,13 @@ public void Test__Lifecycle() Assert.Equal(":LAST[MOD2]", passes[10].Name); Assert.Equal(new[] { patches[20], patches[21] }, passes[10]); - Assert.Equal(":FINAL", passes[11].Name); - Assert.Equal(new[] { patches[22], patches[23] }, passes[11]); + Assert.Equal(":LAST[MOD3]", passes[11].Name); + Assert.Equal(new[] { patches[22] }, passes[11]); + + Assert.Equal(":FINAL", passes[12].Name); + Assert.Equal(new[] { patches[23], patches[24] }, passes[12]); - progress.Received(22).PatchAdded(); + progress.Received(23).PatchAdded(); } } } From 8a95d3709f0dd651deb1fc8294007b847e562045 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 6 Jul 2020 22:55:39 -0700 Subject: [PATCH 323/342] PatchList handles sorting consistently eliminates private class that mostly just passed methods through --- ModuleManager/PatchList.cs | 46 ++++++++------------------------------ 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index e9193f74..f97c8b49 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -37,53 +37,25 @@ public ModPass(string name) public void AddLastPatch(IPatch patch) => lastPass.Add(patch ?? throw new ArgumentNullException(nameof(patch))); } - private class ModPassCollection : IEnumerable - { - private readonly ModPass[] passesArray; - private readonly Dictionary passesDict; - - public ModPassCollection(IEnumerable modList) - { - int count = modList.Count(); - passesArray = new ModPass[count]; - passesDict = new Dictionary(count); - - int i = 0; - foreach (string mod in modList) - { - ModPass pass = new ModPass(mod); - passesArray[i] = pass; - passesDict.Add(mod.ToLowerInvariant(), pass); - i++; - } - } - - public ModPass this[string name] => passesDict[name.ToLowerInvariant()]; - public ModPass this[int index] => passesArray[index]; - - public bool HasMod(string name) => passesDict.ContainsKey(name.ToLowerInvariant()); - - public int Count => passesArray.Length; - - public ArrayEnumerator GetEnumerator() => new ArrayEnumerator(passesArray); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - private readonly Pass insertPatches = new Pass(":INSERT (initial)"); private readonly Pass firstPatches = new Pass(":FIRST"); private readonly Pass legacyPatches = new Pass(":LEGACY (default)"); private readonly Pass finalPatches = new Pass(":FINAL"); - private readonly ModPassCollection modPasses; + private readonly SortedDictionary modPasses = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); private readonly SortedDictionary lastPasses = new SortedDictionary(StringComparer.InvariantCultureIgnoreCase); public PatchList(IEnumerable modList, IEnumerable patches, IPatchProgress progress) { - modPasses = new ModPassCollection(modList ?? throw new ArgumentNullException(nameof(modList))); + if (modList == null) throw new ArgumentNullException(nameof(modList)); if (patches == null) throw new ArgumentNullException(nameof(patches)); if (progress == null) throw new ArgumentNullException(nameof(progress)); + foreach (string mod in modList) + { + modPasses.Add(mod, new ModPass(mod)); + } + foreach (IPatch patch in patches) { if (patch.PassSpecifier is InsertPassSpecifier) @@ -141,7 +113,7 @@ public IEnumerator GetEnumerator() yield return firstPatches; yield return legacyPatches; - foreach (ModPass modPass in modPasses) + foreach (ModPass modPass in modPasses.Values) { yield return modPass.beforePass; yield return modPass.forPass; @@ -162,7 +134,7 @@ private void EnsureMod(string mod) { if (mod == null) throw new ArgumentNullException(nameof(mod)); if (mod == string.Empty) throw new ArgumentException("can't be empty", nameof(mod)); - if (!modPasses.HasMod(mod)) throw new KeyNotFoundException($"Mod '{mod}' not found"); + if (!modPasses.ContainsKey(mod)) throw new KeyNotFoundException($"Mod '{mod}' not found"); } } } From 5168b5c743371838de869745081fda8849c86eb0 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 6 Jul 2020 23:52:39 -0700 Subject: [PATCH 324/342] Address some messages Do some dependency in jection on InterceptLogHandler, no need to hold onto a reference to it as Unity will Remove paramter checks in StreamLogger as StreamWriter does the same checks Ibut keep basic tests for them) --- ModuleManager/Cats/CatAnimator.cs | 4 +++- ModuleManager/Cats/CatManager.cs | 2 +- ModuleManager/Cats/CatMover.cs | 8 ++++--- ModuleManager/Cats/CatOrbiter.cs | 8 ++++--- ModuleManager/Collections/ImmutableStack.cs | 2 +- .../ExceptionIntercept/InterceptLogHandler.cs | 5 ++-- ModuleManager/Extensions/StringExtensions.cs | 2 +- ModuleManager/Fix16.cs | 2 ++ ModuleManager/Logging/StreamLogger.cs | 3 --- ModuleManager/Logging/UnityLogger.cs | 2 +- ModuleManager/MMPatchLoader.cs | 2 -- ModuleManager/MMPatchRunner.cs | 13 ++++------- ModuleManager/ModuleManager.cs | 6 ++--- ModuleManager/NeedsChecker.cs | 2 ++ ModuleManager/NodeMatcher.cs | 8 +++---- ModuleManager/PatchExtractor.cs | 2 ++ ModuleManager/PatchList.cs | 2 -- ModuleManager/Patches/PatchCompiler.cs | 23 ++++++------------- ModuleManager/PostPatchLoader.cs | 2 ++ ModuleManager/Progress/PatchProgress.cs | 2 +- ModuleManager/Threading/TaskStatus.cs | 22 ++++++++---------- ModuleManager/Threading/TaskStatusWrapper.cs | 2 +- ModuleManager/Utils/FileUtils.cs | 12 +++------- .../Collections/MessageQueueTest.cs | 2 +- .../Extensions/IBasicLoggerExtensionsTest.cs | 8 +------ .../Logging/PrefixLoggerTest.cs | 5 ++-- ModuleManagerTests/Logging/QueueLoggerTest.cs | 5 ++-- .../Logging/StreamLoggerTest.cs | 9 ++------ ModuleManagerTests/Logging/UnityLoggerTest.cs | 5 ++-- ModuleManagerTests/NeedsCheckerTest.cs | 11 +++------ ModuleManagerTests/PassTest.cs | 8 ------- ModuleManagerTests/PatchExtractorTest.cs | 6 ----- .../Progress/PatchProgressTest.cs | 6 ++--- 33 files changed, 73 insertions(+), 128 deletions(-) diff --git a/ModuleManager/Cats/CatAnimator.cs b/ModuleManager/Cats/CatAnimator.cs index ed5b2c70..7806d4aa 100644 --- a/ModuleManager/Cats/CatAnimator.cs +++ b/ModuleManager/Cats/CatAnimator.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Diagnostics.CodeAnalysis; using UnityEngine; namespace ModuleManager.Cats @@ -12,9 +13,10 @@ class CatAnimator : MonoBehaviour private SpriteRenderer spriteRenderer; private int spriteIdx; + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void Start() { - spriteRenderer = this.GetComponent(); + spriteRenderer = GetComponent(); spriteRenderer.sortingOrder = 3; StartCoroutine(Animate()); } diff --git a/ModuleManager/Cats/CatManager.cs b/ModuleManager/Cats/CatManager.cs index 744a0ca8..05f671d6 100644 --- a/ModuleManager/Cats/CatManager.cs +++ b/ModuleManager/Cats/CatManager.cs @@ -14,7 +14,7 @@ public static void LaunchCat() InitCats(); GameObject cat = LaunchCat(scale); - CatMover catMover = cat.AddComponent(); + cat.AddComponent(); } public static void LaunchCats() diff --git a/ModuleManager/Cats/CatMover.cs b/ModuleManager/Cats/CatMover.cs index 2e37ac9a..d97a3f7c 100644 --- a/ModuleManager/Cats/CatMover.cs +++ b/ModuleManager/Cats/CatMover.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Diagnostics.CodeAnalysis; using UnityEngine; namespace ModuleManager.Cats @@ -24,12 +24,13 @@ public class CatMover : MonoBehaviour private bool clearTrail = false; // Use this for initialization + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void Start() { - trail = this.GetComponent(); + trail = GetComponent(); trail.sortingOrder = 2; - spriteRenderer = this.GetComponent(); + spriteRenderer = GetComponent(); offsetY = Mathf.FloorToInt(0.2f * Screen.height); @@ -41,6 +42,7 @@ void Start() clearTrail = true; } + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void Update() { if (trail.time <= 0f) diff --git a/ModuleManager/Cats/CatOrbiter.cs b/ModuleManager/Cats/CatOrbiter.cs index 17621c6f..34b1f6e8 100644 --- a/ModuleManager/Cats/CatOrbiter.cs +++ b/ModuleManager/Cats/CatOrbiter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using KSP.UI; using UnityEngine; using Random = UnityEngine.Random; @@ -8,7 +9,7 @@ namespace ModuleManager.Cats { class CatOrbiter : MonoBehaviour { - private static List orbiters = new List(); + private static readonly List orbiters = new List(); private static CatOrbiter sun; @@ -20,7 +21,7 @@ class CatOrbiter : MonoBehaviour private Vector2d force; private float scale = 1; - private double G = 6.67408E-11; + private const double G = 6.67408E-11; public double Mass { @@ -113,13 +114,14 @@ private void DoForces() } } - + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void OnDestroy() { orbiters.Remove(this); TimingManager.FixedUpdateRemove(TimingManager.TimingStage.Earlyish, DoForces); } + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] void FixedUpdate() { //if (this == sun) diff --git a/ModuleManager/Collections/ImmutableStack.cs b/ModuleManager/Collections/ImmutableStack.cs index 5d181567..d710c423 100644 --- a/ModuleManager/Collections/ImmutableStack.cs +++ b/ModuleManager/Collections/ImmutableStack.cs @@ -8,7 +8,7 @@ public class ImmutableStack : IEnumerable { public struct Enumerator : IEnumerator { - private ImmutableStack head; + private readonly ImmutableStack head; private ImmutableStack currentStack; public Enumerator(ImmutableStack stack) diff --git a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs index 6cd3dcd0..471b1fa8 100644 --- a/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs +++ b/ModuleManager/ExceptionIntercept/InterceptLogHandler.cs @@ -16,10 +16,9 @@ class InterceptLogHandler : ILogHandler public static string Warnings { get; private set; } = ""; - public InterceptLogHandler() + public InterceptLogHandler(ILogHandler baseLogHandler) { - baseLogHandler = Debug.unityLogger.logHandler; - Debug.unityLogger.logHandler = this; + this.baseLogHandler = baseLogHandler ?? throw new ArgumentNullException(nameof(baseLogHandler)); gamePathLength = Path.GetFullPath(KSPUtil.ApplicationRootPath).Length; } diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index c4190045..b9136e30 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -18,7 +18,7 @@ public static bool IsBracketBalanced(this string s) return level == 0; } - private static Regex whitespaceRegex = new Regex(@"\s+"); + private static readonly Regex whitespaceRegex = new Regex(@"\s+"); public static string RemoveWS(this string withWhite) { diff --git a/ModuleManager/Fix16.cs b/ModuleManager/Fix16.cs index ab20e6ae..b567eb56 100644 --- a/ModuleManager/Fix16.cs +++ b/ModuleManager/Fix16.cs @@ -1,9 +1,11 @@ using System.Collections; +using System.Diagnostics.CodeAnalysis; namespace ModuleManager { class Fix16 : LoadingSystem { + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] private void Awake() { if (Instance != null) diff --git a/ModuleManager/Logging/StreamLogger.cs b/ModuleManager/Logging/StreamLogger.cs index 74115d3e..d020f64c 100644 --- a/ModuleManager/Logging/StreamLogger.cs +++ b/ModuleManager/Logging/StreamLogger.cs @@ -5,14 +5,11 @@ namespace ModuleManager.Logging { public sealed class StreamLogger : IBasicLogger, IDisposable { - private readonly Stream stream; private readonly StreamWriter streamWriter; private bool disposed = false; public StreamLogger(Stream stream) { - this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); - if (!stream.CanWrite) throw new ArgumentException("must be writable", nameof(stream)); streamWriter = new StreamWriter(stream); } diff --git a/ModuleManager/Logging/UnityLogger.cs b/ModuleManager/Logging/UnityLogger.cs index b31c1003..d51063c4 100644 --- a/ModuleManager/Logging/UnityLogger.cs +++ b/ModuleManager/Logging/UnityLogger.cs @@ -5,7 +5,7 @@ namespace ModuleManager.Logging { public class UnityLogger : IBasicLogger { - private ILogger logger; + private readonly ILogger logger; public UnityLogger(ILogger logger) { diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 1cffe055..d90e29a1 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -8,8 +8,6 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using UnityEngine; -using Debug = UnityEngine.Debug; using ModuleManager.Collections; using ModuleManager.Logging; diff --git a/ModuleManager/MMPatchRunner.cs b/ModuleManager/MMPatchRunner.cs index b812ceed..6c3d63a0 100644 --- a/ModuleManager/MMPatchRunner.cs +++ b/ModuleManager/MMPatchRunner.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.IO; -using UnityEngine; using ModuleManager.Collections; using ModuleManager.Extensions; using ModuleManager.Logging; @@ -14,8 +13,6 @@ namespace ModuleManager { public class MMPatchRunner { - private const float TIME_TO_WAIT_FOR_LOGS = 0.05f; - private readonly IBasicLogger kspLogger; public string Status { get; private set; } = ""; @@ -38,12 +35,10 @@ public IEnumerator Run() QueueLogRunner logRunner = new QueueLogRunner(mmLogQueue); ITaskStatus loggingThreadStatus = BackgroundTask.Start(delegate { - using (StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create))) - { - streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); - logRunner.Run(streamLogger); - streamLogger.Info("Done!"); - } + using StreamLogger streamLogger = new StreamLogger(new FileStream(logPath, FileMode.Create)); + streamLogger.Info("Log started at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); + logRunner.Run(streamLogger); + streamLogger.Info("Done!"); }); // Wait for game database to be initialized for the 2nd time and wait for any plugins to initialize diff --git a/ModuleManager/ModuleManager.cs b/ModuleManager/ModuleManager.cs index 361cb540..dd5e04e5 100644 --- a/ModuleManager/ModuleManager.cs +++ b/ModuleManager/ModuleManager.cs @@ -38,8 +38,6 @@ public class ModuleManager : MonoBehaviour private MMPatchRunner patchRunner; - private InterceptLogHandler interceptLogHandler; - #endregion state private static bool loadedInScene; @@ -80,8 +78,8 @@ internal void Awake() } totalTime.Start(); - - interceptLogHandler = new InterceptLogHandler(); + + Debug.unityLogger.logHandler = new InterceptLogHandler(Debug.unityLogger.logHandler); // Allow loading the background in the loading screen Application.runInBackground = true; diff --git a/ModuleManager/NeedsChecker.cs b/ModuleManager/NeedsChecker.cs index 7e452642..94f2ad29 100644 --- a/ModuleManager/NeedsChecker.cs +++ b/ModuleManager/NeedsChecker.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ModuleManager.Extensions; using ModuleManager.Logging; @@ -20,6 +21,7 @@ public class NeedsChecker : INeedsChecker private readonly IEnumerable mods; private readonly UrlDir gameData; private readonly IPatchProgress progress; + [SuppressMessage("CodeQuality", "IDE0052", Justification = "Reserved for future use")] private readonly IBasicLogger logger; public NeedsChecker(IEnumerable mods, UrlDir gameData, IPatchProgress progress, IBasicLogger logger) diff --git a/ModuleManager/NodeMatcher.cs b/ModuleManager/NodeMatcher.cs index 5c3a59ca..8777de97 100644 --- a/ModuleManager/NodeMatcher.cs +++ b/ModuleManager/NodeMatcher.cs @@ -10,11 +10,9 @@ public interface INodeMatcher public class NodeMatcher : INodeMatcher { - private static readonly char[] sep = { '[', ']' }; - - private string type; - private string[] namePatterns = null; - private string constraints = ""; + private readonly string type; + private readonly string[] namePatterns = null; + private readonly string constraints = ""; public NodeMatcher(string type, string name, string constraints) { diff --git a/ModuleManager/PatchExtractor.cs b/ModuleManager/PatchExtractor.cs index cf476a64..6c8765f4 100644 --- a/ModuleManager/PatchExtractor.cs +++ b/ModuleManager/PatchExtractor.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using ModuleManager.Extensions; using ModuleManager.Logging; using ModuleManager.Patches; @@ -10,6 +11,7 @@ namespace ModuleManager public class PatchExtractor { private readonly IPatchProgress progress; + [SuppressMessage("CodeQuality", "IDE0052", Justification = "Reserved for future use")] private readonly IBasicLogger logger; private readonly INeedsChecker needsChecker; private readonly ITagListParser tagListParser; diff --git a/ModuleManager/PatchList.cs b/ModuleManager/PatchList.cs index f97c8b49..d3990540 100644 --- a/ModuleManager/PatchList.cs +++ b/ModuleManager/PatchList.cs @@ -1,8 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using ModuleManager.Collections; using ModuleManager.Patches; using ModuleManager.Patches.PassSpecifiers; using ModuleManager.Progress; diff --git a/ModuleManager/Patches/PatchCompiler.cs b/ModuleManager/Patches/PatchCompiler.cs index 67387372..6c7e7f30 100644 --- a/ModuleManager/Patches/PatchCompiler.cs +++ b/ModuleManager/Patches/PatchCompiler.cs @@ -13,23 +13,14 @@ public IPatch CompilePatch(ProtoPatch protoPatch) { if (protoPatch == null) throw new ArgumentNullException(nameof(protoPatch)); - switch (protoPatch.command) + return protoPatch.command switch { - case Command.Insert: - return new InsertPatch(protoPatch.urlConfig, protoPatch.nodeType, protoPatch.passSpecifier); - - case Command.Edit: - return new EditPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); - - case Command.Copy: - return new CopyPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); - - case Command.Delete: - return new DeletePatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier); - - default: - throw new ArgumentException("has an invalid command for a root node: " + protoPatch.command, nameof(protoPatch)); - } + Command.Insert => new InsertPatch(protoPatch.urlConfig, protoPatch.nodeType, protoPatch.passSpecifier), + Command.Edit => new EditPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier), + Command.Copy => new CopyPatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier), + Command.Delete => new DeletePatch(protoPatch.urlConfig, new NodeMatcher(protoPatch.nodeType, protoPatch.nodeName, protoPatch.has), protoPatch.passSpecifier), + _ => throw new ArgumentException("has an invalid command for a root node: " + protoPatch.command, nameof(protoPatch)), + }; } } } diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index f92f1fad..619f09d0 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -9,6 +9,7 @@ using ModuleManager.Logging; using static ModuleManager.FilePathRepository; +using System.Diagnostics.CodeAnalysis; namespace ModuleManager { @@ -34,6 +35,7 @@ public static void AddPostPatchCallback(ModuleManagerPostPatchCallback callback) postPatchCallbacks.Add(callback); } + [SuppressMessage("CodeQuality", "IDE0051", Justification = "Called by Unity")] private void Awake() { if (Instance != null) diff --git a/ModuleManager/Progress/PatchProgress.cs b/ModuleManager/Progress/PatchProgress.cs index 3cebf994..61fd0d33 100644 --- a/ModuleManager/Progress/PatchProgress.cs +++ b/ModuleManager/Progress/PatchProgress.cs @@ -8,7 +8,7 @@ public class PatchProgress : IPatchProgress { public ProgressCounter Counter { get; private set; } - private IBasicLogger logger; + private readonly IBasicLogger logger; public float ProgressFraction { diff --git a/ModuleManager/Threading/TaskStatus.cs b/ModuleManager/Threading/TaskStatus.cs index 92f8c237..1d2c6b67 100644 --- a/ModuleManager/Threading/TaskStatus.cs +++ b/ModuleManager/Threading/TaskStatus.cs @@ -4,12 +4,10 @@ namespace ModuleManager.Threading { public class TaskStatus : ITaskStatus { - private bool isRunning = true; - private Exception exception = null; - private object lockObject = new object(); + private readonly object lockObject = new object(); - public bool IsRunning => isRunning; - public Exception Exception => exception; + public bool IsRunning { get; private set; } = true; + public Exception Exception { get; private set; } = null; public bool IsFinished { @@ -17,7 +15,7 @@ public bool IsFinished { lock (lockObject) { - return !isRunning && exception == null; + return !IsRunning && Exception == null; } } } @@ -28,7 +26,7 @@ public bool IsExitedWithError { lock (lockObject) { - return !isRunning && exception != null; + return !IsRunning && Exception != null; } } } @@ -37,8 +35,8 @@ public void Finished() { lock (lockObject) { - if (!isRunning) throw new InvalidOperationException("Task is not running"); - isRunning = false; + if (!IsRunning) throw new InvalidOperationException("Task is not running"); + IsRunning = false; } } @@ -46,9 +44,9 @@ public void Error(Exception exception) { lock(lockObject) { - if (!isRunning) throw new InvalidOperationException("Task is not running"); - this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); - isRunning = false; + if (!IsRunning) throw new InvalidOperationException("Task is not running"); + this.Exception = exception ?? throw new ArgumentNullException(nameof(exception)); + IsRunning = false; } } } diff --git a/ModuleManager/Threading/TaskStatusWrapper.cs b/ModuleManager/Threading/TaskStatusWrapper.cs index eff24f78..1750633c 100644 --- a/ModuleManager/Threading/TaskStatusWrapper.cs +++ b/ModuleManager/Threading/TaskStatusWrapper.cs @@ -4,7 +4,7 @@ namespace ModuleManager.Threading { public class TaskStatusWrapper : ITaskStatus { - private ITaskStatus inner; + private readonly ITaskStatus inner; public TaskStatusWrapper(ITaskStatus inner) { diff --git a/ModuleManager/Utils/FileUtils.cs b/ModuleManager/Utils/FileUtils.cs index e069e14c..818d3cc6 100644 --- a/ModuleManager/Utils/FileUtils.cs +++ b/ModuleManager/Utils/FileUtils.cs @@ -11,15 +11,9 @@ public static string FileSHA(string filename) { if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); - byte[] data = null; - - using (SHA256 sha = SHA256.Create()) - { - using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read)) - { - data = sha.ComputeHash(fs); - } - } + using SHA256 sha = SHA256.Create(); + using FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read); + byte[] data = sha.ComputeHash(fs); return data.ToHex(); } diff --git a/ModuleManagerTests/Collections/MessageQueueTest.cs b/ModuleManagerTests/Collections/MessageQueueTest.cs index 83bf1217..baf35d20 100644 --- a/ModuleManagerTests/Collections/MessageQueueTest.cs +++ b/ModuleManagerTests/Collections/MessageQueueTest.cs @@ -8,7 +8,7 @@ public class MessageQueueTest { private class TestClass { } - private MessageQueue queue = new MessageQueue(); + private readonly MessageQueue queue = new MessageQueue(); [Fact] public void Test__Empty() diff --git a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs index f6c1fad7..74e82d88 100644 --- a/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/IBasicLoggerExtensionsTest.cs @@ -1,7 +1,6 @@ using System; using Xunit; using NSubstitute; -using UnityEngine; using ModuleManager.Logging; using ModuleManager.Extensions; @@ -9,12 +8,7 @@ namespace ModuleManagerTests.Extensions { public class IBasicLoggerExtensionsTest { - private IBasicLogger logger; - - public IBasicLoggerExtensionsTest() - { - logger = Substitute.For(); - } + private readonly IBasicLogger logger = Substitute.For(); [Fact] public void TestInfo() diff --git a/ModuleManagerTests/Logging/PrefixLoggerTest.cs b/ModuleManagerTests/Logging/PrefixLoggerTest.cs index 28a82edc..5aaf0113 100644 --- a/ModuleManagerTests/Logging/PrefixLoggerTest.cs +++ b/ModuleManagerTests/Logging/PrefixLoggerTest.cs @@ -8,12 +8,11 @@ namespace ModuleManagerTests.Logging { public class PrefixLoggerTest { - private IBasicLogger innerLogger; - private PrefixLogger logger; + private readonly IBasicLogger innerLogger = Substitute.For(); + private readonly PrefixLogger logger; public PrefixLoggerTest() { - innerLogger = Substitute.For(); logger = new PrefixLogger("MyMod", innerLogger); } diff --git a/ModuleManagerTests/Logging/QueueLoggerTest.cs b/ModuleManagerTests/Logging/QueueLoggerTest.cs index dc8a1dfd..25e1abbe 100644 --- a/ModuleManagerTests/Logging/QueueLoggerTest.cs +++ b/ModuleManagerTests/Logging/QueueLoggerTest.cs @@ -8,12 +8,11 @@ namespace ModuleManagerTests.Logging { public class QueueLoggerTest { - private IMessageQueue queue; - private QueueLogger logger; + private readonly IMessageQueue queue = Substitute.For>(); + private readonly QueueLogger logger; public QueueLoggerTest() { - queue = Substitute.For>(); logger = new QueueLogger(queue); } diff --git a/ModuleManagerTests/Logging/StreamLoggerTest.cs b/ModuleManagerTests/Logging/StreamLoggerTest.cs index bc613038..296eeac2 100644 --- a/ModuleManagerTests/Logging/StreamLoggerTest.cs +++ b/ModuleManagerTests/Logging/StreamLoggerTest.cs @@ -11,12 +11,10 @@ public class StreamLoggerTest [Fact] public void TestConstructor__StreamNull() { - ArgumentNullException ex = Assert.Throws(delegate + Assert.Throws(delegate { new StreamLogger(null); }); - - Assert.Equal("stream", ex.ParamName); } [Fact] @@ -24,13 +22,10 @@ public void TestConstructor__CantWrite() { using (MemoryStream stream = new MemoryStream(new byte[0], false)) { - ArgumentException ex = Assert.Throws(delegate + Assert.Throws(delegate { new StreamLogger(stream); }); - - Assert.Equal("stream", ex.ParamName); - Assert.Contains("must be writable", ex.Message); } } diff --git a/ModuleManagerTests/Logging/UnityLoggerTest.cs b/ModuleManagerTests/Logging/UnityLoggerTest.cs index 5d820c25..7c4dc4a1 100644 --- a/ModuleManagerTests/Logging/UnityLoggerTest.cs +++ b/ModuleManagerTests/Logging/UnityLoggerTest.cs @@ -9,12 +9,11 @@ namespace ModuleManagerTests.Logging { public class UnityLoggerTest { - private ILogger innerLogger; - private UnityLogger logger; + private readonly ILogger innerLogger = Substitute.For(); + private readonly UnityLogger logger; public UnityLoggerTest() { - innerLogger = Substitute.For(); logger = new UnityLogger(innerLogger); } diff --git a/ModuleManagerTests/NeedsCheckerTest.cs b/ModuleManagerTests/NeedsCheckerTest.cs index d0414a0b..4d3540b9 100644 --- a/ModuleManagerTests/NeedsCheckerTest.cs +++ b/ModuleManagerTests/NeedsCheckerTest.cs @@ -5,7 +5,6 @@ using ModuleManager; using ModuleManager.Logging; using ModuleManager.Progress; -using NodeStack = ModuleManager.Collections.ImmutableStack; namespace ModuleManagerTests { @@ -13,17 +12,13 @@ public class NeedsCheckerTest { private readonly UrlDir gameData; - private readonly IPatchProgress progress; - private readonly IBasicLogger logger; - private NeedsChecker needsChecker; + private readonly IPatchProgress progress = Substitute.For(); + private readonly IBasicLogger logger = Substitute.For(); + private readonly NeedsChecker needsChecker; public NeedsCheckerTest() { gameData = UrlBuilder.CreateGameData(); - - progress = Substitute.For(); - logger = Substitute.For(); - needsChecker = new NeedsChecker(new[] { "mod1", "mod2", "mod/2" }, gameData, progress, logger); } diff --git a/ModuleManagerTests/PassTest.cs b/ModuleManagerTests/PassTest.cs index d2cce882..bc829816 100644 --- a/ModuleManagerTests/PassTest.cs +++ b/ModuleManagerTests/PassTest.cs @@ -2,7 +2,6 @@ using System.Linq; using Xunit; using NSubstitute; -using TestUtils; using ModuleManager; using ModuleManager.Patches; @@ -10,13 +9,6 @@ namespace ModuleManagerTests { public class PassTest { - private UrlDir.UrlFile file; - - public PassTest() - { - file = UrlBuilder.CreateFile("abc/def.cfg"); - } - [Fact] public void TestConstructor__NameNull() { diff --git a/ModuleManagerTests/PatchExtractorTest.cs b/ModuleManagerTests/PatchExtractorTest.cs index 4106486e..49a12583 100644 --- a/ModuleManagerTests/PatchExtractorTest.cs +++ b/ModuleManagerTests/PatchExtractorTest.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Xunit; using NSubstitute; using TestUtils; @@ -386,10 +385,5 @@ private void AssertNoErrors() progress.DidNotReceiveWithAnyArgs().Exception(null, null); progress.DidNotReceiveWithAnyArgs().Exception(null, null, null); } - - private void AssertConfigNodesEqual(ConfigNode expected, ConfigNode observed) - { - Assert.Equal(expected.ToString(), observed.ToString()); - } } } diff --git a/ModuleManagerTests/Progress/PatchProgressTest.cs b/ModuleManagerTests/Progress/PatchProgressTest.cs index 8cde86c2..c78964e3 100644 --- a/ModuleManagerTests/Progress/PatchProgressTest.cs +++ b/ModuleManagerTests/Progress/PatchProgressTest.cs @@ -1,7 +1,6 @@ using System; using Xunit; using NSubstitute; -using UnityEngine; using TestUtils; using ModuleManager; using ModuleManager.Logging; @@ -11,12 +10,11 @@ namespace ModuleManagerTests { public class PatchProgressTest { - private IBasicLogger logger; - private PatchProgress progress; + private readonly IBasicLogger logger = Substitute.For(); + private readonly PatchProgress progress; public PatchProgressTest() { - logger = Substitute.For(); progress = new PatchProgress(logger); } From e0da6ba13bca4dea7181cd009b49691d9fd47df0 Mon Sep 17 00:00:00 2001 From: sarbian Date: Tue, 7 Jul 2020 11:42:00 +0200 Subject: [PATCH 325/342] v4.1.4 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index cf5d1a0c..fba8c4bf 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.1.3")] +[assembly: AssemblyVersion("4.1.4")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 0261dc6dac6b3340357a02c8322d21770be9c09c Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Wed, 30 Sep 2020 22:15:15 -0700 Subject: [PATCH 326/342] Normalize KSP root path KSP makes it weird Addresses confusion in #164 --- ModuleManager/FilePathRepository.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index db15419e..0d1448d7 100644 --- a/ModuleManager/FilePathRepository.cs +++ b/ModuleManager/FilePathRepository.cs @@ -5,20 +5,21 @@ namespace ModuleManager { internal static class FilePathRepository { - internal static readonly string cachePath = Path.Combine(KSPUtil.ApplicationRootPath, "GameData", "ModuleManager.ConfigCache"); + internal static readonly string normalizedRootPath = Path.GetFullPath(KSPUtil.ApplicationRootPath); + internal static readonly string cachePath = Path.Combine(normalizedRootPath, "GameData", "ModuleManager.ConfigCache"); internal static readonly string techTreeFile = Path.Combine("GameData", "ModuleManager.TechTree"); - internal static readonly string techTreePath = Path.Combine(KSPUtil.ApplicationRootPath, techTreeFile); + internal static readonly string techTreePath = Path.Combine(normalizedRootPath, techTreeFile); internal static readonly string physicsFile = Path.Combine("GameData", "ModuleManager.Physics"); - internal static readonly string physicsPath = Path.Combine(KSPUtil.ApplicationRootPath, physicsFile); - internal static readonly string defaultPhysicsPath = Path.Combine(KSPUtil.ApplicationRootPath, "Physics.cfg"); + internal static readonly string physicsPath = Path.Combine(normalizedRootPath, physicsFile); + internal static readonly string defaultPhysicsPath = Path.Combine(normalizedRootPath, "Physics.cfg"); - internal static readonly string partDatabasePath = Path.Combine(KSPUtil.ApplicationRootPath, "PartDatabase.cfg"); + internal static readonly string partDatabasePath = Path.Combine(normalizedRootPath, "PartDatabase.cfg"); - internal static readonly string shaPath = Path.Combine(KSPUtil.ApplicationRootPath, "GameData", "ModuleManager.ConfigSHA"); + internal static readonly string shaPath = Path.Combine(normalizedRootPath, "GameData", "ModuleManager.ConfigSHA"); - internal static readonly string logsDirPath = Path.Combine(KSPUtil.ApplicationRootPath, "Logs", "ModuleManager"); + internal static readonly string logsDirPath = Path.Combine(normalizedRootPath, "Logs", "ModuleManager"); internal static readonly string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); internal static readonly string patchLogPath = Path.Combine(logsDirPath, "MMPatch.log"); } From f6669bf7b7a9a2f0cb818ee757b3d2ed870898bb Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 14 Jan 2021 23:41:11 -0800 Subject: [PATCH 327/342] Set modded physics and reload earlier Do it in post patch, this allows the part loader to pick up changes (e.g. rigidbody min mass) --- ModuleManager/CustomConfigsManager.cs | 10 ---------- ModuleManager/PostPatchLoader.cs | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/ModuleManager/CustomConfigsManager.cs b/ModuleManager/CustomConfigsManager.cs index e2b8689c..d7444363 100644 --- a/ModuleManager/CustomConfigsManager.cs +++ b/ModuleManager/CustomConfigsManager.cs @@ -16,16 +16,6 @@ internal void Start() Log("Setting modded tech tree as the active one"); HighLogic.CurrentGame.Parameters.Career.TechTreeUrl = techTreeFile; } - - if (PhysicsGlobals.PhysicsDatabaseFilename != physicsFile && File.Exists(physicsPath)) - { - Log("Setting modded physics as the active one"); - - PhysicsGlobals.PhysicsDatabaseFilename = physicsFile; - - if (!PhysicsGlobals.Instance.LoadDatabase()) - Log("Something went wrong while setting the active physics config."); - } } public static void Log(String s) diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index 619f09d0..fa1b9df6 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -130,6 +130,8 @@ private IEnumerator Run() logger.Info("Reloading Part Upgrades"); PartUpgradeManager.Handler.FillUpgrades(); + LoadModdedPhysics(); + yield return null; progressTitle = "ModuleManager: Running post patch callbacks"; @@ -209,5 +211,23 @@ private IEnumerator Run() ready = true; } + + private void LoadModdedPhysics() + { + if (PhysicsGlobals.PhysicsDatabaseFilename == physicsFile) return; + + if (!File.Exists(physicsPath)) + { + logger.Error("Physics file not found"); + return; + } + + logger.Info("Setting modded physics as the active one"); + + PhysicsGlobals.PhysicsDatabaseFilename = physicsFile; + + if (!PhysicsGlobals.Instance.LoadDatabase()) + logger.Error("Something went wrong while setting the active physics config."); + } } } From 983b330b259547f05d1ff2ff10bc5828153c7883 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 14 Jan 2021 23:52:56 -0800 Subject: [PATCH 328/342] mark dependencies as copy local false prevents them from showing up in the target directory --- ModuleManager/ModuleManager.csproj | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index d58d8d14..4556194b 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -121,33 +121,42 @@ False + False False + False False + False False + False False + False False + False False + False False + False False + False From b3d5ad47526c43b6d175c775cec77b967055cae7 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 14 Jan 2021 23:56:16 -0800 Subject: [PATCH 329/342] fix remaining .NET 3.5 nuget packages upgrade visual studio runner --- ModuleManagerTests/ModuleManagerTests.csproj | 8 ++++---- ModuleManagerTests/packages.config | 4 ++-- TestUtilsTests/TestUtilsTests.csproj | 8 ++++---- TestUtilsTests/packages.config | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index b4fafe96..7e311b3b 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -1,8 +1,8 @@  - + - + Debug @@ -156,10 +156,10 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index dc5635be..63061ffe 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -11,6 +11,6 @@ - - + + \ No newline at end of file diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 619e6bb4..1d3ff25e 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -1,8 +1,8 @@  - + - + Debug @@ -86,10 +86,10 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + \ No newline at end of file diff --git a/TestUtilsTests/packages.config b/TestUtilsTests/packages.config index bbb4cb82..8cf69e4f 100644 --- a/TestUtilsTests/packages.config +++ b/TestUtilsTests/packages.config @@ -7,6 +7,6 @@ - - + + \ No newline at end of file From 6a32ebb8213c2d7f8bca1d3532b15dcced12a598 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Thu, 14 Jan 2021 23:57:18 -0800 Subject: [PATCH 330/342] Update remaining NuGet packages --- ModuleManagerTests/ModuleManagerTests.csproj | 14 +++++++------- ModuleManagerTests/app.config | 4 ++-- ModuleManagerTests/packages.config | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 7e311b3b..1a82db26 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -40,20 +40,20 @@ - ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + ..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll - - ..\packages\NSubstitute.3.1.0\lib\net46\NSubstitute.dll + + ..\packages\NSubstitute.4.2.2\lib\net46\NSubstitute.dll - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll - - ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll diff --git a/ModuleManagerTests/app.config b/ModuleManagerTests/app.config index 1d152980..fe205877 100644 --- a/ModuleManagerTests/app.config +++ b/ModuleManagerTests/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/ModuleManagerTests/packages.config b/ModuleManagerTests/packages.config index 63061ffe..b159434d 100644 --- a/ModuleManagerTests/packages.config +++ b/ModuleManagerTests/packages.config @@ -1,9 +1,9 @@  - - - - + + + + From da38958f48b34bff2052169e5f84885ec2b7daa1 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 15 Jan 2021 00:04:06 -0800 Subject: [PATCH 331/342] Add specific language markers to all project files seems to get confused otherwise --- ModuleManager/ModuleManager.csproj | 3 +++ ModuleManagerTests/ModuleManagerTests.csproj | 3 +++ TestUtils/TestUtils.csproj | 3 +++ TestUtilsTests/TestUtilsTests.csproj | 3 +++ 4 files changed, 12 insertions(+) diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index 4556194b..20fe0da1 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -33,6 +33,9 @@ False false + + 8.0 + diff --git a/ModuleManagerTests/ModuleManagerTests.csproj b/ModuleManagerTests/ModuleManagerTests.csproj index 1a82db26..f3afbbf5 100644 --- a/ModuleManagerTests/ModuleManagerTests.csproj +++ b/ModuleManagerTests/ModuleManagerTests.csproj @@ -37,6 +37,9 @@ 4 false + + 8.0 + diff --git a/TestUtils/TestUtils.csproj b/TestUtils/TestUtils.csproj index 10073bd0..5c53b27c 100644 --- a/TestUtils/TestUtils.csproj +++ b/TestUtils/TestUtils.csproj @@ -32,6 +32,9 @@ 4 false + + 8.0 + diff --git a/TestUtilsTests/TestUtilsTests.csproj b/TestUtilsTests/TestUtilsTests.csproj index 1d3ff25e..cfe71bf2 100644 --- a/TestUtilsTests/TestUtilsTests.csproj +++ b/TestUtilsTests/TestUtilsTests.csproj @@ -37,6 +37,9 @@ 4 false + + 8.0 + From 838677db5f2d2c17c1ce653489a3caa974110009 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 22 Jan 2021 22:34:18 -0800 Subject: [PATCH 332/342] Ensure string comparison is culture invariant And get rid of message suppresions related to it --- ModuleManager/Extensions/StringExtensions.cs | 9 +++++ ModuleManager/MMPatchLoader.cs | 35 ++++++++----------- .../Extensions/StringExtensionsTest.cs | 30 ++++++++++++++++ 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index b9136e30..634fab7a 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -24,5 +24,14 @@ public static string RemoveWS(this string withWhite) { return whitespaceRegex.Replace(withWhite, ""); } + + public static bool Contains(this string str, string value, out int index) + { + if (str == null) throw new ArgumentNullException(nameof(str)); + if (value == null) throw new ArgumentNullException(nameof(value)); + + index = str.IndexOf(value, StringComparison.CurrentCultureIgnoreCase); + return index != -1; + } } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index d90e29a1..61d993a3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -22,8 +22,6 @@ namespace ModuleManager { - [SuppressMessage("ReSharper", "StringLastIndexOfIsCultureSpecific.1")] - [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] public class MMPatchLoader { private const string PHYSICS_NODE_NAME = "PHYSICSGLOBALS"; @@ -922,7 +920,7 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon ConfigNode newSubMod = new ConfigNode(toPaste.name); newSubMod = ModifyNode(nodeStack.Push(newSubMod), toPaste, context); - if (subName.LastIndexOf(",") > 0 && int.TryParse(subName.Substring(subName.LastIndexOf(",") + 1), out int index)) + if (subName.LastIndexOf(',') > 0 && int.TryParse(subName.Substring(subName.LastIndexOf(',') + 1), out int index)) { // In this case insert the node at position index InsertNode(newNode, newSubMod, index); @@ -945,11 +943,10 @@ public static ConfigNode ModifyNode(NodeStack original, ConfigNode mod, PatchCon // NODE,n will match the nth node (NODE is the same as NODE,0) // NODE,* will match ALL nodes // NODE:HAS[condition] will match ALL nodes with condition - if (subName.Contains(":HAS[")) + if (subName.Contains(":HAS[", out int hasStart)) { - int start = subName.IndexOf(":HAS["); - constraints = subName.Substring(start + 5, subName.LastIndexOf(']') - start - 5); - subName = subName.Substring(0, start); + constraints = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5); + subName = subName.Substring(0, hasStart); } if (subName.Contains(",")) @@ -1117,11 +1114,10 @@ private static ConfigNode RecurseNodeSearch(string path, NodeStack nodeStack, Pa string constraint = ""; int index = 0; - if (subName.Contains(":HAS[")) + if (subName.Contains(":HAS[", out int hasStart)) { - int start = subName.IndexOf(":HAS["); - constraint = subName.Substring(start + 5, subName.LastIndexOf(']') - start - 5); - subName = subName.Substring(0, start); + constraint = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5); + subName = subName.Substring(0, hasStart); } else if (subName.Contains(",")) { @@ -1290,13 +1286,12 @@ private static ConfigNode.Value RecurseVariableSearch(string path, NodeStack nod string constraint = ""; string nodeType, nodeName; int index = 0; - if (subName.Contains(":HAS[")) + if (subName.Contains(":HAS[", out int hasStart)) { - int start = subName.IndexOf(":HAS["); - constraint = subName.Substring(start + 5, subName.LastIndexOf(']') - start - 5); - subName = subName.Substring(0, start); + constraint = subName.Substring(hasStart + 5, subName.LastIndexOf(']') - hasStart - 5); + subName = subName.Substring(0, hasStart); } - else if (subName.Contains(",")) + else if (subName.Contains(',')) { string tag = subName.Split(',')[1]; subName = subName.Split(',')[0]; @@ -1551,11 +1546,11 @@ public static bool CheckConstraints(ConfigNode node, string constraints) constraints = constraintList[0]; string remainingConstraints = ""; - if (constraints.Contains("HAS[")) + if (constraints.Contains(":HAS[", out int hasStart)) { - int start = constraints.IndexOf("HAS[") + 4; - remainingConstraints = constraints.Substring(start, constraintList[0].LastIndexOf(']') - start); - constraints = constraints.Substring(0, start - 5); + hasStart += 4; + remainingConstraints = constraints.Substring(hasStart, constraintList[0].LastIndexOf(']') - hasStart); + constraints = constraints.Substring(0, hasStart - 5); } string[] splits = constraints.Split(contraintSeparators, 3); diff --git a/ModuleManagerTests/Extensions/StringExtensionsTest.cs b/ModuleManagerTests/Extensions/StringExtensionsTest.cs index fdf3b33f..acf9274e 100644 --- a/ModuleManagerTests/Extensions/StringExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/StringExtensionsTest.cs @@ -39,5 +39,35 @@ public void TestRemoveWS() { Assert.Equal("abcdef", " abc \tdef\r\n\t ".RemoveWS()); } + + + [InlineData("abc", "b", true, 1)] + [InlineData("abc", "x", false, -1)] + [Theory] + public void TestContains(string str, string test, bool expectedResult, int expectedIndex) + { + bool result = str.Contains(test, out int index); + Assert.Equal(expectedResult, result); + Assert.Equal(expectedIndex, index); + } + + [Fact] + public void TestContains__NullStr() + { + string s = null; + Assert.Throws(delegate + { + s.Contains("x", out int _x); + }); + } + + [Fact] + public void TestContains__NullValue() + { + Assert.Throws(delegate + { + "abc".Contains(null, out int _x); + }); + } } } From c6163a2e495628e59822d2074da027e70aa85136 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Fri, 22 Jan 2021 22:36:24 -0800 Subject: [PATCH 333/342] Always replace physics On a database reload this will already be true but we still want physics reloading to happen --- ModuleManager/PostPatchLoader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index fa1b9df6..d850e4e1 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -214,8 +214,6 @@ private IEnumerator Run() private void LoadModdedPhysics() { - if (PhysicsGlobals.PhysicsDatabaseFilename == physicsFile) return; - if (!File.Exists(physicsPath)) { logger.Error("Physics file not found"); From 3894ce10ac129611594f2524bf87fef27aafbd85 Mon Sep 17 00:00:00 2001 From: sarbian Date: Sun, 1 Aug 2021 10:42:05 +0200 Subject: [PATCH 334/342] v4.2.0 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index fba8c4bf..0cd2c63f 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.1.4")] +[assembly: AssemblyVersion("4.2.0")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From d9e9264fb2f04bb3e4f0026133bf5b110bd4296d Mon Sep 17 00:00:00 2001 From: Alvin Meng Date: Sun, 1 Aug 2021 11:51:32 -0400 Subject: [PATCH 335/342] Fix off-by-one string indexing in constraint checking Also change string comparison type to `StringComparison.Ordinal`, which should be the correct type according to https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings. --- ModuleManager/Extensions/StringExtensions.cs | 2 +- ModuleManager/MMPatchLoader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index 634fab7a..55720367 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -30,7 +30,7 @@ public static bool Contains(this string str, string value, out int index) if (str == null) throw new ArgumentNullException(nameof(str)); if (value == null) throw new ArgumentNullException(nameof(value)); - index = str.IndexOf(value, StringComparison.CurrentCultureIgnoreCase); + index = str.IndexOf(value, StringComparison.Ordinal); return index != -1; } } diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index 61d993a3..cf3418c3 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -1548,7 +1548,7 @@ public static bool CheckConstraints(ConfigNode node, string constraints) string remainingConstraints = ""; if (constraints.Contains(":HAS[", out int hasStart)) { - hasStart += 4; + hasStart += 5; remainingConstraints = constraints.Substring(hasStart, constraintList[0].LastIndexOf(']') - hasStart); constraints = constraints.Substring(0, hasStart - 5); } From c60e3c537a352de35b02d5fa79a1a6c712a83e9a Mon Sep 17 00:00:00 2001 From: Alvin Meng Date: Sun, 1 Aug 2021 15:08:13 -0400 Subject: [PATCH 336/342] Undo string comparison change. --- ModuleManager/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Extensions/StringExtensions.cs b/ModuleManager/Extensions/StringExtensions.cs index 55720367..634fab7a 100644 --- a/ModuleManager/Extensions/StringExtensions.cs +++ b/ModuleManager/Extensions/StringExtensions.cs @@ -30,7 +30,7 @@ public static bool Contains(this string str, string value, out int index) if (str == null) throw new ArgumentNullException(nameof(str)); if (value == null) throw new ArgumentNullException(nameof(value)); - index = str.IndexOf(value, StringComparison.Ordinal); + index = str.IndexOf(value, StringComparison.CurrentCultureIgnoreCase); return index != -1; } } From 86e60c36036c9fa6cd24d9abdc679f452059a8b2 Mon Sep 17 00:00:00 2001 From: sarbian Date: Sun, 1 Aug 2021 21:52:17 +0200 Subject: [PATCH 337/342] v4.2.1 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index 0cd2c63f..ef0b7100 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.2.0")] +[assembly: AssemblyVersion("4.2.1")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From ebe2895e3a55384fc8c39c9e0f964a5e39d3ede9 Mon Sep 17 00:00:00 2001 From: NathanKell Date: Thu, 16 Jun 2022 21:35:54 -0700 Subject: [PATCH 338/342] Support patching Localization tokens. * Support wildcards in nodetype matching so you can do @*,* {} * Support # in value names since loc names start with # * Tell Localizer to reload the language after MM finishes --- ModuleManager/MMPatchLoader.cs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index cf3418c3..5278758b 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -231,6 +231,9 @@ public IEnumerable Run() } } + if (KSP.Localization.Localizer.Instance != null) + KSP.Localization.Localizer.SwitchToLanguage(KSP.Localization.Localizer.CurrentLanguage); + logger.Info(status + "\n" + errors); patchSw.Stop(); @@ -564,7 +567,7 @@ private void StatusUpdate(IPatchProgress progress, string activity = null) #region Applying Patches // Name is group 1, index is group 2, vector related filed is group 3, vector separator is group 4, operator is group 5 - private static readonly Regex parseValue = new Regex(@"([\w\&\-\.\?\*+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?"); + private static readonly Regex parseValue = new Regex(@"([\w\&\-\.\?\*\#+/^!\(\) ]+(?:,[^*\d][\w\&\-\.\?\*\(\) ]*)*)(?:,(-?[0-9\*]+))?(?:\[((?:[0-9\*]+)+)(?:,(.))?\])?"); // ModifyNode applies the ConfigNode mod as a 'patch' to ConfigNode original, then returns the patched ConfigNode. // it uses FindConfigNodeIn(src, nodeType, nodeName, nodeTag) to recurse. @@ -1712,19 +1715,26 @@ public static ConfigNode FindConfigNodeIn( string nodeName = null, int index = 0) { - ConfigNode[] nodes = src.GetNodes(nodeType); - if (nodes.Length == 0) + List nodes = new List(); + int c = src.nodes.Count; + for(int i = 0; i < c; ++i) + { + if (WildcardMatch(src.nodes[i].name, nodeType)) + nodes.Add(src.nodes[i]); + } + int nodeCount = nodes.Count; + if (nodeCount == 0) return null; if (nodeName == null) { if (index >= 0) - return nodes[Math.Min(index, nodes.Length - 1)]; - return nodes[Math.Max(0, nodes.Length + index)]; + return nodes[Math.Min(index, nodeCount - 1)]; + return nodes[Math.Max(0, nodeCount + index)]; } ConfigNode last = null; if (index >= 0) { - for (int i = 0; i < nodes.Length; ++i) + for (int i = 0; i < nodeCount; ++i) { if (nodes[i].HasValue("name") && WildcardMatch(nodes[i].GetValue("name"), nodeName)) { @@ -1735,7 +1745,7 @@ public static ConfigNode FindConfigNodeIn( } return last; } - for (int i = nodes.Length - 1; i >= 0; --i) + for (int i = nodeCount - 1; i >= 0; --i) { if (nodes[i].HasValue("name") && WildcardMatch(nodes[i].GetValue("name"), nodeName)) { From a303e0a7118f3506cc1546b70b03acc04263a6b7 Mon Sep 17 00:00:00 2001 From: sarbian Date: Sat, 18 Jun 2022 20:22:56 +0200 Subject: [PATCH 339/342] v4.2.2 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index ef0b7100..e4e038f0 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.2.1")] +[assembly: AssemblyVersion("4.2.2")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly, From 2ba651e41aebba1758e57b679f01bb8592b2d536 Mon Sep 17 00:00:00 2001 From: Jonathan Bayer Date: Sun, 14 May 2023 18:19:50 -0400 Subject: [PATCH 340/342] Added , FileShare.ReadWrite to the File.Open, to allow MM to read files which are already opened by KSP --- ModuleManager/Utils/FileUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Utils/FileUtils.cs b/ModuleManager/Utils/FileUtils.cs index 818d3cc6..fcf04482 100644 --- a/ModuleManager/Utils/FileUtils.cs +++ b/ModuleManager/Utils/FileUtils.cs @@ -12,7 +12,7 @@ public static string FileSHA(string filename) if (!File.Exists(filename)) throw new FileNotFoundException("File does not exist", filename); using SHA256 sha = SHA256.Create(); - using FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read); + using FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); byte[] data = sha.ComputeHash(fs); return data.ToHex(); From b22136a8302b592626533f864413cb7b37a32126 Mon Sep 17 00:00:00 2001 From: siimav Date: Fri, 19 May 2023 23:51:58 +0300 Subject: [PATCH 341/342] Fix invalid modded physics cfg path being fed to KSP when using the faulty PDLauncher workaround --- ModuleManager/PostPatchLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/PostPatchLoader.cs b/ModuleManager/PostPatchLoader.cs index d850e4e1..1bccce78 100644 --- a/ModuleManager/PostPatchLoader.cs +++ b/ModuleManager/PostPatchLoader.cs @@ -222,7 +222,7 @@ private void LoadModdedPhysics() logger.Info("Setting modded physics as the active one"); - PhysicsGlobals.PhysicsDatabaseFilename = physicsFile; + PhysicsGlobals.PhysicsDatabaseFilename = physicsPath; if (!PhysicsGlobals.Instance.LoadDatabase()) logger.Error("Something went wrong while setting the active physics config."); From c4561925f983e7ae81d9dfd4d11356a35cb6b9b6 Mon Sep 17 00:00:00 2001 From: sarbian Date: Mon, 3 Jul 2023 20:01:38 +0200 Subject: [PATCH 342/342] v4.2.3 --- ModuleManager/Properties/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ModuleManager/Properties/AssemblyInfo.cs b/ModuleManager/Properties/AssemblyInfo.cs index e4e038f0..12a660e1 100644 --- a/ModuleManager/Properties/AssemblyInfo.cs +++ b/ModuleManager/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion("4.2.2")] +[assembly: AssemblyVersion("4.2.3")] [assembly: KSPAssembly("ModuleManager", 2, 5)] // The following attributes are used to specify the signing key for the assembly,