Skip to content

Commit 6975961

Browse files
committed
Version 1.5.0
1 parent 36d0a1c commit 6975961

18 files changed

+578
-102
lines changed

CHANGES

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
+ VERSION 1.5.0; 2023-05-07
2+
- Added ability to parse list items in 'AppDescription' configuration and transpose to HTML.
3+
- Added XML validation of AppStream metadata (will now warn if invalid prior to build).
4+
- Bugfix: The configuration property 'IconFiles' handled icon names of form "name.NxN.png", but failed with "name.N.png".
5+
- The ${PRIME_CATEGORY} macro now defaults to "Utility" if PrimeCategory' configuration is omitted.
6+
- Added extensive new unit testing
7+
- Minor changes to configuration documentation
8+
- Other minor changes
9+
- Ships with: appimagetool 13 (2020-12-31)
10+
- Tested against: rpmbuild RPM version 4.18.1, dpkg 1.21.21, flatpak-builder 1.2.3, InnoSetup 6.2.2
11+
112
+ VERSION 1.4.1; 2023-05-06
213
- Bugfix #16: AppImage and Flatpak builds rejected AppStream metadata if conf.AppDescription was empty. Now defaults to AppShortSummary.
314
- Bugfix #15: AppImage and Flatpak builds rejected AppStream metadata if conf.AppChangeFileEmpty.

PupNet.Test/AppStreamTest.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// -----------------------------------------------------------------------------
2+
// PROJECT : PupNet
3+
// COPYRIGHT : Andy Thomas (C) 2022-23
4+
// LICENSE : GPL-3.0-or-later
5+
// HOMEPAGE : https://github.com/kuiperzone/PupNet
6+
//
7+
// PupNet is free software: you can redistribute it and/or modify it under
8+
// the terms of the GNU Affero General Public License as published by the Free Software
9+
// Foundation, either version 3 of the License, or (at your option) any later version.
10+
//
11+
// PupNet is distributed in the hope that it will be useful, but WITHOUT
12+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13+
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License along
16+
// with PupNet. If not, see <https://www.gnu.org/licenses/>.
17+
// -----------------------------------------------------------------------------
18+
19+
using System.Xml.Linq;
20+
21+
namespace KuiperZone.PupNet.Test;
22+
23+
public class AppStreamTest
24+
{
25+
[Fact]
26+
public void MetaTemplate_ExpandsToValidXML()
27+
{
28+
var host = new BuildHost(new DummyConf(PackageKind.AppImage));
29+
30+
// Must be escaped
31+
var test = host.Macros.Expand(MetaTemplates.MetaInfo, true);
32+
33+
// Console.WriteLine("+++++");
34+
// Console.WriteLine(test);
35+
// Console.WriteLine("+++++");
36+
37+
XDocument.Parse(test);
38+
}
39+
40+
[Fact]
41+
public void MetaTemplate_NoAppDescription_ExpandsToValidXML()
42+
{
43+
var host = new BuildHost(new DummyConf(PackageKind.AppImage, nameof(ConfigurationReader.AppDescription)));
44+
var test = host.Macros.Expand(MetaTemplates.MetaInfo, true);
45+
46+
XDocument.Parse(test);
47+
}
48+
49+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// -----------------------------------------------------------------------------
2+
// PROJECT : PupNet
3+
// COPYRIGHT : Andy Thomas (C) 2022-23
4+
// LICENSE : GPL-3.0-or-later
5+
// HOMEPAGE : https://github.com/kuiperzone/PupNet
6+
//
7+
// PupNet is free software: you can redistribute it and/or modify it under
8+
// the terms of the GNU Affero General Public License as published by the Free Software
9+
// Foundation, either version 3 of the License, or (at your option) any later version.
10+
//
11+
// PupNet is distributed in the hope that it will be useful, but WITHOUT
12+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13+
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License along
16+
// with PupNet. If not, see <https://www.gnu.org/licenses/>.
17+
// -----------------------------------------------------------------------------
18+
19+
using System.Runtime.InteropServices;
20+
21+
namespace KuiperZone.PupNet.Test;
22+
23+
/// <summary>
24+
/// This goes a little beyond the scope of unit test, and performs an "integration test" against <see cref="BuildHost"/>.
25+
/// To do this, we must have installed all the third-party builder-tools and we will actually produce a dummy package
26+
/// output for each test. Although not ideal, it is necessary as testing for each package output kind prior to releasing
27+
/// the software is otherwise an intensive and error prone exercise. Here, we run the tests only in RELEASE builds only
28+
/// as each test execution blocks. It is recommended to run the tests with "dotnet test -c Release" ON MOST PLATFORMS
29+
/// prior to releasing a new version of pupnet. NOTE. Cannot be used with Flatpak or Windows Setup (damn!).
30+
/// </summary>
31+
public class BuildHostIntegrationTest
32+
{
33+
[Fact]
34+
public void BuildZip_EnsureBuildSucceedsAndOutputExists()
35+
{
36+
// We can always test zip
37+
Assert_BuildPackage(PackageKind.Zip, false);
38+
Assert_BuildPackage(PackageKind.Zip, true);
39+
}
40+
41+
42+
#if !DEBUG
43+
[Fact]
44+
public void BuildThirdParty_EnsureBuildSucceedsAndOutputExists()
45+
{
46+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
47+
{
48+
Assert_BuildPackage(PackageKind.AppImage, false);
49+
Assert_BuildPackage(PackageKind.AppImage, true);
50+
51+
Assert_BuildPackage(PackageKind.Deb, false);
52+
Assert_BuildPackage(PackageKind.Deb, true);
53+
54+
Assert_BuildPackage(PackageKind.Rpm, false);
55+
Assert_BuildPackage(PackageKind.Rpm, true);
56+
}
57+
}
58+
#endif
59+
60+
private void Assert_BuildPackage(PackageKind kind, bool complete, bool assertOutput = true)
61+
{
62+
string? metadata = null;
63+
string? manifest = null;
64+
var conf = new TestConfiguration(kind, complete);
65+
66+
try
67+
{
68+
var host = new BuildHost(conf);
69+
70+
// Must create build outside of BuildHost.Run()
71+
host.Builder.Create(host.ExpandedDesktop, host.ExpandedMetaInfo);
72+
metadata = host.ExpandedMetaInfo;
73+
manifest = host.Builder.ManifestContent;
74+
75+
// Regression test - test for correct icon
76+
if (host.Builder.IsLinuxExclusive)
77+
{
78+
// If we do not define icon (i.e complete == false),
79+
// we still get default icon on Linux
80+
Assert.NotNull(host.Builder.PrimaryIcon);
81+
82+
if (complete)
83+
{
84+
// Icon provided in config below
85+
// Always chooses SVG if one is provided over PNG
86+
Assert.EndsWith("Icon.svg", host.Builder.PrimaryIcon);
87+
}
88+
else
89+
{
90+
// Default icon is built into application.
91+
// We have set DesktopTerminal to true below, so expect "terminal" icon
92+
// We will accept either SVG or max size PNG
93+
bool hasSvg = host.Builder.PrimaryIcon.Contains("terminal.svg");;
94+
bool hasPng = host.Builder.PrimaryIcon.Contains("terminal.256x256");
95+
Assert.True(hasSvg || hasPng);
96+
}
97+
}
98+
99+
if (host.Builder.IsOsxExclusive)
100+
{
101+
// Test here
102+
}
103+
104+
if (host.Builder.IsWindowsExclusive)
105+
{
106+
// Windows? We are not declaring Windows icon currently - must be null
107+
Assert.Null(host.Builder.PrimaryIcon);
108+
}
109+
110+
111+
// We do NOT call dotnet publish, and must create dummy app file, otherwise build will fail.
112+
var appPath = Path.Combine(host.Builder.BuildAppBin, host.Builder.AppExecName);
113+
File.WriteAllText(appPath, "Dummy app binary");
114+
115+
host.Builder.BuildPackage();
116+
117+
if (assertOutput)
118+
{
119+
// Check both file and directory (RPM output a directory)
120+
Assert.True(File.Exists(host.Builder.OutputPath) || Directory.Exists(host.Builder.OutputPath));
121+
}
122+
}
123+
catch(Exception e)
124+
{
125+
Console.WriteLine("==============================");
126+
Console.WriteLine($"BUILD FAILED: {kind}, {complete}");
127+
Console.WriteLine("==============================");
128+
Console.WriteLine(e);
129+
Console.WriteLine();
130+
131+
// For debug if fails
132+
Console.WriteLine("==============================");
133+
Console.WriteLine($"CONFIGURATION: {kind}, {complete}");
134+
Console.WriteLine("==============================");
135+
Console.WriteLine(conf.ToString());
136+
Console.WriteLine();
137+
138+
Console.WriteLine("==============================");
139+
Console.WriteLine($"METADATA: {kind}, {complete}");
140+
Console.WriteLine("==============================");
141+
Console.WriteLine(metadata ?? "[NONE]");
142+
Console.WriteLine();
143+
144+
Console.WriteLine("==============================");
145+
Console.WriteLine($"MANIFEST: {kind}, {complete}");
146+
Console.WriteLine("==============================");
147+
Console.WriteLine(manifest ?? "[NONE]");
148+
149+
throw;
150+
}
151+
finally
152+
{
153+
Directory.Delete(conf.OutputDirectory, true);
154+
}
155+
}
156+
157+
private class TestConfiguration : ConfigurationReader
158+
{
159+
public TestConfiguration(PackageKind kind, bool complete)
160+
: base(new ArgumentReader($"-k {kind} -y --verbose"), Create(complete))
161+
{
162+
// NB. We need to skip prompt above
163+
}
164+
165+
private static string[] Create(bool complete)
166+
{
167+
// Use unique temporary directory for everything
168+
var workDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
169+
Directory.CreateDirectory(workDir);
170+
171+
var lines = new List<string>();
172+
173+
// The idea here is to include only the minimal set of configuration we expect to build successfully
174+
lines.Add($"{nameof(ConfigurationReader.AppBaseName)} = 'HelloWorld'");
175+
lines.Add($"{nameof(ConfigurationReader.AppFriendlyName)} = Hello World");
176+
lines.Add($"{nameof(ConfigurationReader.AppId)} = \"net.example.helloworld\"");
177+
lines.Add($"{nameof(ConfigurationReader.AppVersionRelease)} = 5.4.3[2]");
178+
lines.Add($"{nameof(ConfigurationReader.AppShortSummary)} = Test <application> only");
179+
lines.Add($"{nameof(ConfigurationReader.AppLicenseId)} = LicenseRef-LICENSE");
180+
181+
lines.Add($"{nameof(ConfigurationReader.PublisherName)} = Kuiper Zone");
182+
lines.Add($"{nameof(ConfigurationReader.PublisherLinkUrl)} = https://kuiper.zone");
183+
lines.Add($"{nameof(ConfigurationReader.PublisherEmail)} = [email protected]");
184+
185+
lines.Add($"{nameof(ConfigurationReader.DesktopTerminal)} = true");
186+
187+
lines.Add($"{nameof(ConfigurationReader.OutputDirectory)} = {workDir}");
188+
189+
// IMPORTANT - SDK must be installed
190+
lines.Add($"{nameof(ConfigurationReader.FlatpakPlatformRuntime)} = org.freedesktop.Platform");
191+
lines.Add($"{nameof(ConfigurationReader.FlatpakPlatformSdk)} = org.freedesktop.Sdk");
192+
lines.Add($"{nameof(ConfigurationReader.FlatpakPlatformVersion)} = \"22.00\"");
193+
lines.Add($"{nameof(ConfigurationReader.SetupMinWindowsVersion)} = 10");
194+
195+
// Always include metafile We actually need to create the file here
196+
var metapath = Path.Combine(workDir, "app.metainfo.xml");
197+
File.WriteAllText(metapath, MetaTemplates.MetaInfo);
198+
lines.Add($"{nameof(ConfigurationReader.MetaFile)} = {metapath}");
199+
200+
if (complete)
201+
{
202+
// Here we add extended configuration we consider optional extras
203+
lines.Add($"{nameof(ConfigurationReader.PackageName)} = HelloWorld");
204+
lines.Add($"{nameof(ConfigurationReader.AppDescription)} = Test description\n\n* bullet1\n- bullet2\nLine2");
205+
lines.Add($"{nameof(ConfigurationReader.PublisherCopyright)} = Copyright Kuiper Zone");
206+
lines.Add($"{nameof(ConfigurationReader.PublisherLinkName)} = kuiper.zone");
207+
208+
lines.Add($"{nameof(ConfigurationReader.DesktopNoDisplay)} = false");
209+
lines.Add($"{nameof(ConfigurationReader.PrimeCategory)} = Development");
210+
lines.Add($"{nameof(ConfigurationReader.AppImageVersionOutput)} = true");
211+
lines.Add($"{nameof(ConfigurationReader.FlatpakFinishArgs)} = --socket=wayland;--socket=fallback-x11;--filesystem=host;--share=network");
212+
213+
lines.Add($"{nameof(ConfigurationReader.RpmAutoReq)} = true");
214+
lines.Add($"{nameof(ConfigurationReader.RpmAutoProv)} = false");
215+
216+
lines.Add($"{nameof(ConfigurationReader.SetupVersionOutput)} = true");
217+
lines.Add($"{nameof(ConfigurationReader.SetupCommandPrompt)} = Command Prompt");
218+
219+
// Need to create dummy icons in order to get the thing to build (we cheat with dummy files).
220+
// However, we can't write dummy ico for windows because Setup would fail (needs to be valid icon)
221+
var icons = new List<string>();
222+
icons.Add(Path.Combine(workDir, "Icon.32.png"));
223+
File.WriteAllText(icons[^1], "Dummy file");
224+
225+
icons.Add(Path.Combine(workDir, "Icon.64.png"));
226+
File.WriteAllText(icons[^1], "Dummy file");
227+
228+
icons.Add(Path.Combine(workDir, "Icon.svg"));
229+
File.WriteAllText(icons[^1], "Dummy file");
230+
231+
lines.Add($"{nameof(ConfigurationReader.IconFiles)} = {string.Join(';', icons)}");
232+
}
233+
234+
return lines.ToArray();
235+
}
236+
}
237+
}

PupNet.Test/ConfigurationReaderTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void AppDescription_Optional_DecodeOK()
6161
{
6262
// Use of angle brackets deliberate
6363
var lines = Create().AppDescription;
64-
var exp = new string[] { "Line1", "<Line2>", "", "Line3 has ${LINE3_VAR}" };
64+
var exp = new string[] { "Para1-Line1", "<Para1-Line2>", "", "- Bullet1", "* Bullet2", "Para2-Line1 has ${MACRO_VAR}" };
6565

6666
Assert.Equal(exp, lines);
6767
Assert.Empty(Create(nameof(ConfigurationReader.AppDescription)).AppDescription);

PupNet.Test/DummyConf.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
namespace KuiperZone.PupNet.Test;
2020

2121
/// <summary>
22-
/// Dummy configuration with test values. Used in unit tests.
22+
/// Dummy configuration with test values. The values are expected by unit tests,
23+
/// so changing them will break the tests.
2324
/// </summary>
2425
public class DummyConf : ConfigurationReader
2526
{
@@ -34,7 +35,7 @@ public DummyConf(ArgumentReader args)
3435
}
3536

3637
/// <summary>
37-
/// Creates demo of given kind. If omit is not null, the property name given
38+
/// Creates demo of given kind. For unit test - if omit is not null, the property name given
3839
/// will be removed from the content, leaving the value to fall back to its default.
3940
/// </summary>
4041
public DummyConf(PackageKind kind, string? omit = null)
@@ -53,7 +54,7 @@ private static string[] Create(string? omit = null)
5354
lines.Add($"{nameof(ConfigurationReader.AppVersionRelease)} = 5.4.3[2]");
5455
lines.Add($"{nameof(ConfigurationReader.PackageName)} = HelloWorld");
5556
lines.Add($"{nameof(ConfigurationReader.AppShortSummary)} = Test <application> only");
56-
lines.Add($"{nameof(ConfigurationReader.AppDescription)} = \n Line1\n<Line2>\n\n Line3 has ${{LINE3_VAR}}\n");
57+
lines.Add($"{nameof(ConfigurationReader.AppDescription)} = \n Para1-Line1\n<Para1-Line2>\n\n- Bullet1\n* Bullet2\nPara2-Line1 has ${{MACRO_VAR}}\n");
5758
lines.Add($"{nameof(ConfigurationReader.AppLicenseId)} = LicenseRef-LICENSE");
5859
lines.Add($"{nameof(ConfigurationReader.AppLicenseFile)} = LICENSE");
5960
lines.Add($"{nameof(ConfigurationReader.AppChangeFile)} = CHANGELOG");
@@ -69,7 +70,7 @@ private static string[] Create(string? omit = null)
6970
lines.Add($"{nameof(ConfigurationReader.DesktopTerminal)} = False");
7071
lines.Add($"{nameof(ConfigurationReader.PrimeCategory)} = Development");
7172
lines.Add($"{nameof(ConfigurationReader.DesktopFile)} = app.desktop");
72-
lines.Add($"{nameof(ConfigurationReader.IconFiles)} = Assets/Icon.32x32.png; Assets/Icon.64x64.png; Assets/Icon.ico; Assets/Icon.svg;");
73+
lines.Add($"{nameof(ConfigurationReader.IconFiles)} = Assets/Icon.32x32.png; Assets/Icon.x48.png; Assets/Icon.64.png; Assets/Icon.ico; Assets/Icon.svg;");
7374
lines.Add($"{nameof(ConfigurationReader.MetaFile)} = metainfo.xml");
7475

7576
lines.Add($"{nameof(ConfigurationReader.DotnetProjectPath)} = HelloProject");

0 commit comments

Comments
 (0)