snapcraft packs nowhere outside the project it builds, so Todo 0.1.0's release stopped
after its snap was built ("Cannot create packages outside of the project tree").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
240 lines
7.4 KiB
Dart
240 lines
7.4 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:test/test.dart';
|
|
|
|
import '../../apps/learn/release.dart';
|
|
import '../../apps/todo/release.dart';
|
|
import '../../apps/train/release.dart';
|
|
import '../../tool/src/release/apps.dart';
|
|
import '../../tool/src/release/version.dart';
|
|
import 'fixtures.dart';
|
|
|
|
void main() {
|
|
test("an app's release names its files and tags by its id", () {
|
|
expect(testApp.tagPrefix, 'notes-v');
|
|
expect(testApp.pubspec, 'apps/notes/halkyon_notes_flutter/pubspec.yaml');
|
|
expect(testApp.instance, 'notes.halkyon.eu');
|
|
expect(
|
|
testApp.apkFile(const Version(0, 19, 0)),
|
|
'halkyon-notes-0.19.0.apk',
|
|
);
|
|
expect(
|
|
testApp.snapFile(const Version(0, 19, 0)),
|
|
'halkyon-notes_0.19.0_amd64.snap',
|
|
);
|
|
});
|
|
|
|
test('a configuration that would release something wrong is refused', () {
|
|
expect(testApp.problems(), isEmpty);
|
|
expect(
|
|
testApp.copyWith(id: 'Notes App').problems(),
|
|
contains('The id "Notes App" is not lower-case letters and digits.'),
|
|
);
|
|
expect(
|
|
testApp.copyWith(serverUrl: 'http://notes.halkyon.eu/').problems(),
|
|
contains("The server URL http://notes.halkyon.eu/ isn't HTTPS."),
|
|
);
|
|
expect(
|
|
testApp.copyWith(serverUrl: 'https://notes.halkyon.eu').problems(),
|
|
contains(
|
|
"The server URL https://notes.halkyon.eu doesn't end with a slash, "
|
|
'as the app is built with it.',
|
|
),
|
|
);
|
|
expect(
|
|
testApp
|
|
.copyWith(flutterPackage: 'apps/todo/halkyon_todo_flutter')
|
|
.problems(),
|
|
contains(
|
|
"The Flutter package apps/todo/halkyon_todo_flutter isn't in "
|
|
'apps/notes.',
|
|
),
|
|
);
|
|
expect(
|
|
testApp.copyWith(applicationId: 'notes').problems(),
|
|
contains("The application ID notes isn't one Android takes."),
|
|
);
|
|
});
|
|
|
|
test("Learn's release is registered, sound, and names files that exist", () {
|
|
expect(releasedApps['learn'], same(learnRelease));
|
|
expect(learnRelease.problems(), isEmpty);
|
|
// The test runs in the repository's root, as `dart test` does.
|
|
for (final path in [
|
|
learnRelease.pubspec,
|
|
learnRelease.android.certificateFile,
|
|
...learnRelease.releaseFiles,
|
|
]) {
|
|
expect(
|
|
FileSystemEntity.typeSync(path),
|
|
isNot(FileSystemEntityType.notFound),
|
|
reason: path,
|
|
);
|
|
}
|
|
expect(
|
|
File('${learnRelease.snap!.directory}/snapcraft.yaml').readAsStringSync(),
|
|
contains('name: ${learnRelease.snap!.name}\n'),
|
|
);
|
|
});
|
|
|
|
test("Train's release is registered, sound, names files that exist, and "
|
|
'has a key of its own', () {
|
|
expect(releasedApps['train'], same(trainRelease));
|
|
expect(trainRelease.problems(), isEmpty);
|
|
for (final path in [
|
|
trainRelease.pubspec,
|
|
trainRelease.android.certificateFile,
|
|
...trainRelease.releaseFiles,
|
|
]) {
|
|
expect(
|
|
FileSystemEntity.typeSync(path),
|
|
isNot(FileSystemEntityType.notFound),
|
|
reason: path,
|
|
);
|
|
}
|
|
expect(trainRelease.snap, isNull);
|
|
expect(
|
|
trainRelease.android.certificateFile,
|
|
isNot(learnRelease.android.certificateFile),
|
|
);
|
|
expect(
|
|
trainRelease.android.defaultKeystore,
|
|
isNot(learnRelease.android.defaultKeystore),
|
|
);
|
|
expect(
|
|
File(
|
|
'${trainRelease.fdroid!.listing}/metadata/'
|
|
'${trainRelease.applicationId}.yml',
|
|
).readAsStringSync(),
|
|
contains('NonFreeDep'),
|
|
);
|
|
});
|
|
|
|
test("Todo's release is registered, sound, names files that exist, and "
|
|
'has a key of its own', () {
|
|
expect(releasedApps['todo'], same(todoRelease));
|
|
expect(todoRelease.problems(), isEmpty);
|
|
for (final path in [
|
|
todoRelease.pubspec,
|
|
todoRelease.android.certificateFile,
|
|
...todoRelease.releaseFiles,
|
|
]) {
|
|
expect(
|
|
FileSystemEntity.typeSync(path),
|
|
isNot(FileSystemEntityType.notFound),
|
|
reason: path,
|
|
);
|
|
}
|
|
for (final other in [learnRelease, trainRelease]) {
|
|
expect(
|
|
todoRelease.android.certificateFile,
|
|
isNot(other.android.certificateFile),
|
|
);
|
|
expect(
|
|
todoRelease.android.defaultKeystore,
|
|
isNot(other.android.defaultKeystore),
|
|
);
|
|
expect(
|
|
todoRelease.android.keystoreVariable,
|
|
isNot(other.android.keystoreVariable),
|
|
);
|
|
}
|
|
final listing = File(
|
|
'${todoRelease.fdroid!.listing}/metadata/'
|
|
'${todoRelease.applicationId}.yml',
|
|
).readAsStringSync();
|
|
expect(listing, contains('NonFreeNet'));
|
|
expect(
|
|
File('${todoRelease.fdroid!.listing}/config/categories.yml')
|
|
.readAsStringSync(),
|
|
contains(
|
|
'\n${RegExp(r'Categories:\n - (.+)\n').firstMatch(listing)![1]}:\n',
|
|
),
|
|
);
|
|
});
|
|
|
|
group("Todo's snap, built in a clone of its own with its folder in place of "
|
|
"the root's snap/", () {
|
|
final snap = todoRelease.snap!;
|
|
final recipe = File('${snap.directory}/snapcraft.yaml').readAsStringSync();
|
|
|
|
test("is not the root's, and is built outside the repository", () {
|
|
expect(learnRelease.snap!.atRoot, isTrue);
|
|
expect(snap.atRoot, isFalse);
|
|
expect(
|
|
snap.projectPath('/home/someone'),
|
|
'/home/someone/.cache/halkyon-release/snap/halkyon-todo',
|
|
);
|
|
});
|
|
|
|
test('is packed inside its clone, as snapcraft packs nowhere outside the '
|
|
"project, and Learn's into the release's folder", () {
|
|
expect(
|
|
snap.packPath(home: '/home/someone', root: '/repo', out: 'build/x'),
|
|
'/home/someone/.cache/halkyon-release/snap/halkyon-todo',
|
|
);
|
|
expect(
|
|
learnRelease.snap!.packPath(
|
|
home: '/home/someone',
|
|
root: '/repo',
|
|
out: 'build/x',
|
|
),
|
|
'/repo/build/x',
|
|
);
|
|
});
|
|
|
|
test('names the snap and the part the release tool takes', () {
|
|
expect(recipe, contains('name: ${snap.name}\n'));
|
|
expect(recipe, contains('\n ${snap.part}:\n after: [flutter]\n'));
|
|
expect(recipe, contains('adopt-info: ${snap.part} '));
|
|
});
|
|
|
|
test('builds Todo for its instance', () {
|
|
expect(recipe, contains('cd ${todoRelease.flutterPackage}\n'));
|
|
expect(
|
|
recipe,
|
|
contains('--dart-define=SERVER_URL=${todoRelease.serverUrl}\n'),
|
|
);
|
|
});
|
|
|
|
test('has the files it names, as they will stand in the clone', () {
|
|
// In the clone, the folder is snap/, the root's snap/local/ beside it.
|
|
for (final match in RegExp(
|
|
r'(?<![\w/$])snap/([\w./-]+)',
|
|
).allMatches(recipe)) {
|
|
final inClone = match[1]!;
|
|
final path = inClone.startsWith('local')
|
|
? 'snap/$inClone'
|
|
: '${snap.directory}/$inClone';
|
|
if (inClone == 'snapcraft.yaml') {
|
|
continue;
|
|
}
|
|
expect(
|
|
FileSystemEntity.typeSync(path),
|
|
isNot(FileSystemEntityType.notFound),
|
|
reason: match[0],
|
|
);
|
|
}
|
|
expect(
|
|
File('${snap.directory}/gui/${snap.name}.desktop').readAsStringSync(),
|
|
contains('Exec=${snap.name}\n'),
|
|
);
|
|
});
|
|
});
|
|
|
|
test("every snap's recipe builds with the Flutter CI pins", () {
|
|
final pinned = RegExp(
|
|
r'^\s*FLUTTER_VERSION:\s*(\S+)',
|
|
multiLine: true,
|
|
).firstMatch(File('.forgejo/workflows/ci.yaml').readAsStringSync())![1];
|
|
for (final app in releasedApps.values) {
|
|
if (app.snap case final snap?) {
|
|
expect(
|
|
File('${snap.directory}/snapcraft.yaml').readAsStringSync(),
|
|
contains("source-tag: '$pinned'"),
|
|
reason: app.id,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|