xamarin-macios/tests/xtro-sharpie/xtro-sanity/Sanitizer.cs

344 строки
11 KiB
C#

using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
namespace Extrospection {
class Sanitizer {
static List<string> platforms;
static List<string> Platforms {
get {
if (platforms != null)
return platforms;
platforms = new List<string> (4);
foreach (var line in File.ReadAllLines ("../../Make.config")) {
var eq = line.IndexOf ('=');
if (eq == -1)
continue;
if (!line.StartsWith ("INCLUDE_", StringComparison.Ordinal))
continue;
switch (line.Substring (0, eq)) {
case "INCLUDE_IOS":
platforms.Add ("iOS");
break;
case "INCLUDE_TVOS":
platforms.Add ("tvOS");
break;
case "INCLUDE_WATCH":
platforms.Add ("watchOS");
break;
case "INCLUDE_MAC":
platforms.Add ("macOS");
break;
case "INCLUDE_MACCATALYST":
platforms.Add ("MacCatalyst");
break;
}
}
return platforms;
}
}
static bool IsEntry (string line)
{
return line.Length > 0 && line [0] == '!';
}
// Entries are correct if each line
// * is empty; or
// * starts with a '!' (entries); or
// * starts with a "#" (comments) except for
// * "#!" since it's a commented entry (and should not be committed)
static void CorrectEntries (List<string> entries, string filename)
{
foreach (var entry in entries) {
if (IsEntry (entry))
continue;
if (entry.Length == 0)
continue;
if (entry [0] != '#') {
Log ($"?bad-entry? '{entry}' in '{filename}'");
} else if (entry [1] == '!') {
Log ($"?bad-comment? '{entry}' in '{filename}'");
}
}
}
// Comments can be duplicated but not entries
static void UniqueEntries (List<string> entries, string filename)
{
for (int i = 0; i < entries.Count; i++) {
var entry = entries [i];
if (!IsEntry (entry))
continue;
for (int j = i + 1; j < entries.Count; j++) {
if (entry == entries [j])
Log ($"?dupe-entry? {entry} in '{filename}'");
}
}
}
// it's either common (for all/most) or platform specific - not both
static void NoDuplicateInCommonIgnores ()
{
foreach (var kvp in commons) {
var fx = kvp.Key;
var common = kvp.Value;
foreach (var platform in Platforms) {
var p = Path.Combine (directory, $"{platform}-{fx}.ignore");
if (!File.Exists (p))
continue;
foreach (var entry in File.ReadAllLines (p)) {
if (!IsEntry (entry))
continue;
if (!common.Contains (entry))
continue;
Log ($"?dupe-common? Entry '{entry}' in both 'common-{fx}.ignore' and '{Path.GetFileName (p)}' files");
}
}
}
}
// it's either something in our todo list or something we can ignore - not both
static void NoIgnoredTodo ()
{
foreach (var file in Directory.GetFiles (directory, "*.todo")) {
if (!IsIncluded (file))
continue;
var last = file.LastIndexOf ('-');
var fx = file.Substring (last + 1, file.Length - last - 6);
// check if it's in common or in the same platform
if (commons.TryGetValue (fx, out var common)) {
foreach (var entry in File.ReadAllLines (file)) {
if (!IsEntry (entry))
continue;
if (common.Contains (entry))
Log ($"?dupe-todo? Entry '{entry}' in both 'common-{fx}.ignore' and '{Path.GetFileName (file)}' files");
}
}
var platform = Path.ChangeExtension (file, ".ignore");
if (File.Exists (platform)) {
var specific = new List<string> (File.ReadAllLines (platform));
foreach (var entry in File.ReadAllLines (file)) {
if (!IsEntry (entry))
continue;
if (specific.Contains (entry))
Log ($"?dupe-todo? Entry '{entry}' in both '{Path.GetFileName (platform)}' and '{Path.GetFileName (file)}' files");
}
}
}
}
static bool IsIncluded (string file)
{
var name = Path.GetFileName (file);
foreach (var p in Platforms) {
if (name.StartsWith (p, StringComparison.Ordinal))
return true;
}
return false;
}
static void NoFixedTodo ()
{
foreach (var file in Directory.GetFiles (directory, "*.todo")) {
if (!IsIncluded (file))
continue;
var last = file.LastIndexOf ('-');
var fx = file.Substring (last + 1, file.Length - last - 6);
var raw = Path.ChangeExtension (file, ".raw");
var failures = new List<string> ();
var entries = File.ReadAllLines (file);
if (File.Exists (raw)) {
var specific = new List<string> (File.ReadAllLines (raw));
foreach (var entry in entries) {
if (!IsEntry (entry))
continue;
if (!specific.Contains (entry)) {
Log ($"?fixed-todo? Entry '{entry}' in '{Path.GetFileName (file)}' is not found in corresponding '{Path.GetFileName (raw)}' file");
failures.Add (entry);
}
}
} else {
// no .raw then everything is fixed
foreach (var entry in entries) {
if (!IsEntry (entry))
continue;
Log ($"?fixed-todo? Entry '{entry}' in '{Path.GetFileName (file)}' might be fixed since there's no corresponding '{Path.GetFileName (raw)}' file");
failures.Add (entry);
}
}
if (failures.Count > 0 && !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("AUTO_SANITIZE"))) {
var sanitized = new List<string> (entries);
foreach (var failure in failures)
sanitized.Remove (failure);
File.WriteAllLines (file, sanitized);
// since we are in AUTO_SANITIZE, if the file is empty, remove it.
if (sanitized.Count == 0) {
File.Delete (file);
}
}
}
}
static void NoEmptyTodo ()
{
foreach (var file in Directory.GetFiles (directory, "*.todo")) {
if (!IsIncluded (file))
continue;
if (!(File.ReadLines(file).Count() > 0)) {
Log ($"?empty-todo? File '{Path.GetFileName (file)}' is empty. Empty todo files should be removed.");
}
}
}
static string directory;
static Dictionary<string, List<string>> commons = new Dictionary<string, List<string>> ();
static int count;
public static void Log (string s)
{
Console.WriteLine (s);
count++;
}
public static int Main (string [] args)
{
directory = args.Length == 0 ? "." : args [0];
Environment.CurrentDirectory = directory;
// cache stuff
foreach (var file in Directory.GetFiles (directory, "common-*.ignore")) {
var path = Path.GetFileName (file);
var fx = path.Substring (7, path.Length - 14);
var common = new List<string> (File.ReadAllLines (file));
commons.Add (fx, common);
}
// *.ignore validations
// basic sanity for ignores
foreach (var kvp in commons) {
var fx = kvp.Key;
var common = kvp.Value;
CorrectEntries (common, $"common-{fx}.ignore");
}
foreach (var file in Directory.GetFiles (directory, "*.ignore")) {
if (!IsIncluded (file))
continue;
var filename = Path.GetFileName (file);
// already processed from cache - don't reload them
if (filename.StartsWith ("common-", StringComparison.Ordinal))
continue;
var entries = new List<string> (File.ReadAllLines (file));
CorrectEntries (entries, filename);
}
// uniqueness - check that each .ignore file has no duplicate
foreach (var kvp in commons) {
var fx = kvp.Key;
var common = kvp.Value;
UniqueEntries (common, $"common-{fx}.ignore");
}
foreach (var file in Directory.GetFiles (directory, "*.ignore")) {
var entries = new List<string> (File.ReadAllLines (file));
UniqueEntries (entries, Path.GetFileName (file));
}
// platform specific ignore entries should *not* be duplicated in common-*.ignore
NoDuplicateInCommonIgnores ();
// TODO entries present in all platforms .ignore files should be moved to common
// ignored entries should all exists in the unfiltered .unclassified (raw)
// * common-{fx}.ignored must be part of _at least_ one *-{fx}.raw file
foreach (var kvp in commons) {
var fx = kvp.Key;
var common = kvp.Value;
//ExistingCommonEntries (common, $"common-{fx}.ignore");
List<string> [] raws = new List<string> [Platforms.Count];
for (int i=0; i < raws.Length; i++) {
var fname = Path.Combine (directory, $"{Platforms[i]}-{fx}.raw");
if (File.Exists (fname))
raws [i] = new List<string> (File.ReadAllLines (fname));
else
raws [i] = new List<string> ();
}
var failures = new List<string> ();
foreach (var entry in common) {
if (!entry.StartsWith ("!", StringComparison.Ordinal))
continue;
bool found = false;
foreach (var platform in raws) {
found = platform.Contains (entry);
if (found)
break;
}
if (!found) {
Log ($"?unknown-entry? {entry} in 'common-{fx}.ignore'");
failures.Add (entry);
}
}
if (failures.Count > 0 && !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("AUTO_SANITIZE"))) {
var sanitized = new List<string> (common);
foreach (var failure in failures)
sanitized.Remove (failure);
File.WriteAllLines (Path.Combine (directory, $"common-{fx}.ignore"), sanitized);
}
}
// * a platform ignore must existing in it's corresponding raw file
foreach (var file in Directory.GetFiles (directory, "*.ignore")) {
if (!IsIncluded (file))
continue;
var shortname = Path.GetFileName (file);
if (shortname.StartsWith ("common-", StringComparison.Ordinal))
continue;
// FIXME temporary hack for old data files
if (!shortname.Contains ("-"))
continue;
var rawfile = Path.ChangeExtension (file, ".raw");
var raws = new List<string> (File.Exists (rawfile) ? File.ReadAllLines (rawfile) : Array.Empty<string> ());
var failures = new List<string> ();
var lines = File.ReadAllLines (file);
foreach (var entry in lines) {
if (!entry.StartsWith ("!", StringComparison.Ordinal))
continue;
if (raws.Contains (entry))
continue;
Log ($"?unknown-entry? {entry} in '{shortname}'");
failures.Add (entry);
}
if (failures.Count > 0 && !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("AUTO_SANITIZE"))) {
var sanitized = new List<string> (lines);
foreach (var failure in failures)
sanitized.Remove (failure);
File.WriteAllLines (file, sanitized);
}
}
// *.todo validations
// entries in .todo files should *not* be present in *.ignore files
NoIgnoredTodo ();
// entries in .todo should be found in .raw files - else it's likely fixed (and out of date)
NoFixedTodo ();
// empty files should be removed
NoEmptyTodo ();
if (count == 0)
Console.WriteLine ("Sanity check passed");
else
Console.WriteLine ($"Sanity check failed ({count})");
// useful when updating stuff locally - we report but we don't fail
return Environment.GetEnvironmentVariable ("XTRO_SANITY_SKIP") == "1" ? 0 : count;
}
}
}