Esempio n. 1
0
 public static void AddDeviceName(StringBuilder args, string device_name)
 {
     if (!string.IsNullOrEmpty(device_name))
     {
         args.Append(" --devname ");
         args.Append(Harness.Quote(device_name));
     }
 }
        public static async Task KillTreeAsync(int pid, Log log, bool diagnostics = true)
        {
            var pids = new List <int> ();

            GetChildrenPS(log, pids, pid);
            log.WriteLine($"Pids to kill: {string.Join (", ", pids.Select ((v) => v.ToString ()).ToArray ())}");
            if (diagnostics)
            {
                using (var ps = new Process()) {
                    log.WriteLine("Writing process list:");
                    ps.StartInfo.FileName  = "ps";
                    ps.StartInfo.Arguments = "-A -o pid,ruser,ppid,pgid,%cpu=%CPU,%mem=%MEM,flags=FLAGS,lstart,rss,vsz,tty,state,time,command";
                    await ps.RunAsync(log, true, TimeSpan.FromSeconds(5));
                }

                foreach (var diagnose_pid in pids)
                {
                    var template = Path.GetTempFileName();
                    try {
                        var commands = new StringBuilder();
                        using (var dbg = new Process()) {
                            commands.AppendLine($"process attach --pid {diagnose_pid}");
                            commands.AppendLine("thread list");
                            commands.AppendLine("thread backtrace all");
                            commands.AppendLine("detach");
                            commands.AppendLine("quit");
                            dbg.StartInfo.FileName  = "/usr/bin/lldb";
                            dbg.StartInfo.Arguments = $"--source {Harness.Quote (template)}";
                            File.WriteAllText(template, commands.ToString());

                            log.WriteLine($"Printing backtrace for pid={pid}");
                            await dbg.RunAsync(log, true, TimeSpan.FromSeconds(30));
                        }
                    } finally {
                        try {
                            File.Delete(template);
                        } catch {
                            // Don't care
                        }
                    }
                }
            }

            using (var kill = new Process()) {
                kill.StartInfo.FileName = "kill";
                // Send SIGABRT since that produces a crash report
                // lldb may fail to attach to system processes, but crash reports will still be produced with potentially helpful stack traces.
                kill.StartInfo.Arguments = "-6 " + string.Join(" ", pids.Select((v) => v.ToString()).ToArray());
                await kill.RunAsync(log, true, TimeSpan.FromSeconds(2.5));
            }

            using (var kill = new Process()) {
                kill.StartInfo.FileName = "kill";
                // send kill -9 anyway as a last resort
                kill.StartInfo.Arguments = "-9 " + string.Join(" ", pids.Select((v) => v.ToString()).ToArray());
                await kill.RunAsync(log, true, TimeSpan.FromSeconds(2.5));
            }
        }
Esempio n. 3
0
        public static async Task KillEverythingAsync(Log log)
        {
            await ProcessHelper.ExecuteCommandAsync("launchctl", "remove com.apple.CoreSimulator.CoreSimulatorService", log, TimeSpan.FromSeconds(10));

            var to_kill = new string [] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService" };

            await ProcessHelper.ExecuteCommandAsync("killall", "-9 " + string.Join(" ", to_kill.Select((v) => Harness.Quote(v)).ToArray()), log, TimeSpan.FromSeconds(10));

            foreach (var dir in new string [] {
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", "com.apple.watchsimulator.savedState"),
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", "com.apple.iphonesimulator.savedState"),
            })
            {
                try {
                    if (Directory.Exists(dir))
                    {
                        Directory.Delete(dir, true);
                    }
                } catch (Exception e) {
                    log.WriteLine("Could not delete the directory '{0}': {1}", dir, e.Message);
                }
            }
        }
Esempio n. 4
0
        public void StartCapture()
        {
            writer     = new StreamWriter(new FileStream(LogPath, FileMode.Create));
            streamEnds = new CountdownEvent(2);

            process = new Process();
            process.StartInfo.FileName = Harness.MlaunchPath;
            var sb = new StringBuilder();

            sb.Append("--logdev ");
            sb.Append("--sdkroot ").Append(Harness.Quote(Harness.XcodeRoot)).Append(' ');
            AppRunner.AddDeviceName(sb, DeviceName);
            process.StartInfo.Arguments              = sb.ToString();
            process.StartInfo.UseShellExecute        = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError  = true;
            process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => {
                if (e.Data == null)
                {
                    streamEnds.Signal();
                }
                else
                {
                    lock (writer) {
                        writer.WriteLine(e.Data);
                    }
                }
            };
            process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => {
                if (e.Data == null)
                {
                    streamEnds.Signal();
                }
                else
                {
                    lock (writer) {
                        writer.WriteLine(e.Data);
                    }
                }
            };
            Harness.Log(1, "{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();
        }
Esempio n. 5
0
        public async Task EndCaptureAsync(TimeSpan timeout)
        {
            // Check for crash reports
            var crash_report_search_done    = false;
            var crash_report_search_timeout = timeout.TotalSeconds;
            var watch = new Stopwatch();

            watch.Start();
            do
            {
                var end_crashes = await Harness.CreateCrashReportsSnapshotAsync(Log, !Device, DeviceName);

                end_crashes.ExceptWith(InitialSet);
                Reports = end_crashes;
                if (end_crashes.Count > 0)
                {
                    Log.WriteLine("Found {0} new crash report(s)", end_crashes.Count);
                    List <LogFile> crash_reports;
                    if (!Device)
                    {
                        crash_reports = new List <LogFile> (end_crashes.Count);
                        foreach (var path in end_crashes)
                        {
                            var logPath = Path.Combine(LogDirectory, Path.GetFileName(path));
                            File.Copy(path, logPath, true);
                            crash_reports.Add(Logs.CreateFile("Crash report: " + Path.GetFileName(path), logPath));
                        }
                    }
                    else
                    {
                        // Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench
                        // (if we put them in /tmp, they'd never be deleted).
                        var downloaded_crash_reports = new List <LogFile> ();
                        foreach (var file in end_crashes)
                        {
                            var crash_report_target = Logs.CreateFile("Crash report: " + Path.GetFileName(file), Path.Combine(LogDirectory, Path.GetFileName(file)));
                            var sb = new StringBuilder();
                            sb.Append(" --download-crash-report=").Append(Harness.Quote(file));
                            sb.Append(" --download-crash-report-to=").Append(Harness.Quote(crash_report_target.Path));
                            sb.Append(" --sdkroot ").Append(Harness.Quote(Harness.XcodeRoot));
                            if (!string.IsNullOrEmpty(DeviceName))
                            {
                                sb.Append(" --devname ").Append(Harness.Quote(DeviceName));
                            }
                            var result = await ProcessHelper.ExecuteCommandAsync(Harness.MlaunchPath, sb.ToString(), Log, TimeSpan.FromMinutes(1));

                            if (result.Succeeded)
                            {
                                Log.WriteLine("Downloaded crash report {0} to {1}", file, crash_report_target.Path);
                                crash_report_target = await Harness.SymbolicateCrashReportAsync(Log, crash_report_target);

                                Logs.Add(crash_report_target);
                                downloaded_crash_reports.Add(crash_report_target);
                            }
                            else
                            {
                                Log.WriteLine("Could not download crash report {0}", file);
                            }
                        }
                        crash_reports = downloaded_crash_reports;
                    }
                    foreach (var cp in crash_reports)
                    {
                        Harness.LogWrench("@MonkeyWrench: AddFile: {0}", cp.Path);
                        Log.WriteLine("    {0}", cp.Path);
                    }
                    crash_report_search_done = true;
                }
                else
                {
                    if (watch.Elapsed.TotalSeconds > crash_report_search_timeout)
                    {
                        crash_report_search_done = true;
                    }
                    else
                    {
                        Log.WriteLine("No crash reports, waiting a second to see if the crash report service just didn't complete in time ({0})", (int)(crash_report_search_timeout - watch.Elapsed.TotalSeconds));
                        Thread.Sleep(TimeSpan.FromSeconds(1));
                    }
                }
            } while (!crash_report_search_done);
        }
Esempio n. 6
0
        public async Task <int> RunAsync()
        {
            CrashReportSnapshot crash_reports;
            LogStream           device_system_log = null;
            LogStream           listener_log      = null;
            Log run_log = main_log;

            Initialize();

            if (!isSimulator)
            {
                FindDevice();
            }

            crash_reports = new CrashReportSnapshot()
            {
                Device       = !isSimulator,
                DeviceName   = device_name,
                Harness      = Harness,
                Log          = main_log,
                Logs         = Logs,
                LogDirectory = LogDirectory,
            };

            var args = new StringBuilder();

            if (!string.IsNullOrEmpty(Harness.XcodeRoot))
            {
                args.Append(" --sdkroot ").Append(Harness.XcodeRoot);
            }
            for (int i = -1; i < Harness.Verbosity; i++)
            {
                args.Append(" -v ");
            }
            args.Append(" -argument=-connection-mode -argument=none");              // This will prevent the app from trying to connect to any IDEs
            args.Append(" -argument=-app-arg:-autostart");
            args.Append(" -setenv=NUNIT_AUTOSTART=true");
            args.Append(" -argument=-app-arg:-autoexit");
            args.Append(" -setenv=NUNIT_AUTOEXIT=true");
            args.Append(" -argument=-app-arg:-enablenetwork");
            args.Append(" -setenv=NUNIT_ENABLE_NETWORK=true");
            // detect if we are using a jenkins bot.
            if (Harness.InJenkins)
            {
                args.Append(" -setenv=NUNIT_ENABLE_XML_OUTPUT=true");
            }

            if (isSimulator)
            {
                args.Append(" -argument=-app-arg:-hostname:127.0.0.1");
                args.Append(" -setenv=NUNIT_HOSTNAME=127.0.0.1");
            }
            else
            {
                var ips         = new StringBuilder();
                var ipAddresses = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()).AddressList;
                for (int i = 0; i < ipAddresses.Length; i++)
                {
                    if (i > 0)
                    {
                        ips.Append(',');
                    }
                    ips.Append(ipAddresses [i].ToString());
                }

                args.AppendFormat(" -argument=-app-arg:-hostname:{0}", ips.ToString());
                args.AppendFormat(" -setenv=NUNIT_HOSTNAME={0}", ips.ToString());
            }
            var transport = mode == "watchos" ? "HTTP" : "TCP";

            args.AppendFormat(" -argument=-app-arg:-transport:{0}", transport);
            args.AppendFormat(" -setenv=NUNIT_TRANSPORT={0}", transport);

            SimpleListener listener;

            switch (transport)
            {
            case "HTTP":
                listener = new SimpleHttpListener();
                break;

            case "TCP":
                listener = new SimpleTcpListener();
                break;

            default:
                throw new NotImplementedException();
            }
            listener_log      = Logs.CreateStream(LogDirectory, string.Format("test-{0}-{1:yyyyMMdd_HHmmss}.log", mode, DateTime.Now), "Test log");
            listener.TestLog  = listener_log;
            listener.Log      = main_log;
            listener.AutoExit = true;
            listener.Address  = System.Net.IPAddress.Any;
            listener.Initialize();

            args.AppendFormat(" -argument=-app-arg:-hostport:{0}", listener.Port);
            args.AppendFormat(" -setenv=NUNIT_HOSTPORT={0}", listener.Port);

            listener.StartAsync();

            var cancellation_source = new CancellationTokenSource();
            var timed_out           = false;

            ThreadPool.QueueUserWorkItem((v) =>
            {
                if (!listener.WaitForConnection(TimeSpan.FromMinutes(Harness.LaunchTimeout)))
                {
                    cancellation_source.Cancel();
                    main_log.WriteLine("Test launch timed out after {0} minute(s).", Harness.LaunchTimeout);
                    timed_out = true;
                }
                else
                {
                    main_log.WriteLine("Test run started");
                }
            });

            foreach (var kvp in Harness.EnvironmentVariables)
            {
                args.AppendFormat(" -setenv={0}={1}", kvp.Key, kvp.Value);
            }

            bool?success        = null;
            bool launch_failure = false;

            if (isExtension)
            {
                switch (extension)
                {
                case Extension.TodayExtension:
                    args.Append(isSimulator ? " --launchsimbundleid" : " --launchdevbundleid");
                    args.Append(" todayviewforextensions:");
                    args.Append(BundleIdentifier);
                    args.Append(" --observe-extension ");
                    args.Append(Harness.Quote(launchAppPath));
                    break;

                case Extension.WatchKit2:
                default:
                    throw new NotImplementedException();
                }
            }
            else
            {
                args.Append(isSimulator ? " --launchsim " : " --launchdev ");
                args.Append(Harness.Quote(launchAppPath));
            }

            if (isSimulator)
            {
                if (!await FindSimulatorAsync())
                {
                    return(1);
                }

                if (mode != "watchos")
                {
                    var stderr_tty = Marshal.PtrToStringAuto(ttyname(2));
                    if (!string.IsNullOrEmpty(stderr_tty))
                    {
                        args.Append(" --stdout=").Append(Harness.Quote(stderr_tty));
                        args.Append(" --stderr=").Append(Harness.Quote(stderr_tty));
                    }
                    else
                    {
                        var stdout_log = Logs.CreateFile("Standard output", Path.Combine(LogDirectory, "stdout.log"));
                        var stderr_log = Logs.CreateFile("Standard error", Path.Combine(LogDirectory, "stderr.log"));
                        args.Append(" --stdout=").Append(Harness.Quote(stdout_log.FullPath));
                        args.Append(" --stderr=").Append(Harness.Quote(stderr_log.FullPath));
                    }
                }

                var systemLogs = new List <CaptureLog> ();
                foreach (var sim in simulators)
                {
                    // Upload the system log
                    main_log.WriteLine("System log for the '{1}' simulator is: {0}", sim.SystemLog, sim.Name);
                    bool isCompanion = sim != simulator;

                    var log = new CaptureLog(sim.SystemLog, entire_file: Harness.Action != HarnessAction.Jenkins)
                    {
                        Path        = Path.Combine(LogDirectory, sim.Name + ".log"),
                        Description = isCompanion ? "System log (companion)" : "System log",
                    };
                    log.StartCapture();
                    Logs.Add(log);
                    systemLogs.Add(log);
                    Harness.LogWrench("@MonkeyWrench: AddFile: {0}", log.Path);
                }

                main_log.WriteLine("*** Executing {0}/{1} in the simulator ***", appName, mode);

                if (EnsureCleanSimulatorState)
                {
                    foreach (var sim in simulators)
                    {
                        await sim.PrepareSimulatorAsync(main_log, bundle_identifier);
                    }
                }

                args.Append(" --device=:v2:udid=").Append(simulator.UDID).Append(" ");

                await crash_reports.StartCaptureAsync();

                main_log.WriteLine("Starting test run");

                var result = await ProcessHelper.ExecuteCommandAsync(Harness.MlaunchPath, args.ToString(), run_log, TimeSpan.FromMinutes(Harness.Timeout), cancellation_token : cancellation_source.Token);

                if (result.TimedOut)
                {
                    timed_out = true;
                    success   = false;
                    main_log.WriteLine("Test run timed out after {0} minute(s).", Harness.Timeout);
                }
                else if (result.Succeeded)
                {
                    main_log.WriteLine("Test run completed");
                    success = true;
                }
                else
                {
                    main_log.WriteLine("Test run failed");
                    success = false;
                }

                if (!success.Value)
                {
                    // find pid
                    var pid = -1;
                    using (var reader = run_log.GetReader()) {
                        while (!reader.EndOfStream)
                        {
                            var line = reader.ReadLine();
                            if (line.StartsWith("Application launched. PID = ", StringComparison.Ordinal))
                            {
                                var pidstr = line.Substring("Application launched. PID = ".Length);
                                if (!int.TryParse(pidstr, out pid))
                                {
                                    main_log.WriteLine("Could not parse pid: {0}", pidstr);
                                }
                            }
                            else if (line.Contains("Xamarin.Hosting: Launched ") && line.Contains(" with pid "))
                            {
                                var pidstr = line.Substring(line.LastIndexOf(' '));
                                if (!int.TryParse(pidstr, out pid))
                                {
                                    main_log.WriteLine("Could not parse pid: {0}", pidstr);
                                }
                            }
                            else if (line.Contains("error MT1008"))
                            {
                                launch_failure = true;
                            }
                        }
                    }
                    if (pid > 0)
                    {
                        var launchTimedout = cancellation_source.IsCancellationRequested;
                        var timeoutType    = launchTimedout ? "Launch" : "Completion";
                        var timeoutValue   = launchTimedout ? Harness.LaunchTimeout : Harness.Timeout;
                        main_log.WriteLine($"{timeoutType} timed out after {timeoutValue}");
                        await Process_Extensions.KillTreeAsync(pid, main_log, true);
                    }
                    else
                    {
                        main_log.WriteLine("Could not find pid in mtouch output.");
                    }
                }

                listener.Cancel();

                // cleanup after us
                if (EnsureCleanSimulatorState)
                {
                    await SimDevice.KillEverythingAsync(main_log);
                }

                foreach (var log in systemLogs)
                {
                    log.StopCapture();
                }
            }
            else
            {
                main_log.WriteLine("*** Executing {0}/{1} on device '{2}' ***", appName, mode, device_name);

                if (mode == "watchos")
                {
                    args.Append(" --attach-native-debugger");                      // this prevents the watch from backgrounding the app.
                }
                else
                {
                    args.Append(" --wait-for-exit");
                }

                AddDeviceName(args);

                device_system_log = Logs.CreateStream(LogDirectory, $"device-{device_name}-{DateTime.Now:yyyyMMdd_HHmmss}.log", "Device log");
                var logdev = new DeviceLogCapturer()
                {
                    Harness    = Harness,
                    Log        = device_system_log,
                    DeviceName = device_name,
                };
                logdev.StartCapture();

                await crash_reports.StartCaptureAsync();

                main_log.WriteLine("Starting test run");

                var result = await ProcessHelper.ExecuteCommandAsync(Harness.MlaunchPath, args.ToString(), main_log, TimeSpan.FromMinutes(Harness.Timeout), cancellation_token : cancellation_source.Token);

                if (result.TimedOut)
                {
                    timed_out = true;
                    success   = false;
                    main_log.WriteLine("Test run timed out after {0} minute(s).", Harness.Timeout);
                }
                else if (result.Succeeded)
                {
                    main_log.WriteLine("Test run completed");
                    success = true;
                }
                else
                {
                    main_log.WriteLine("Test run failed");
                    success = false;
                }

                logdev.StopCapture();

                // Upload the system log
                if (File.Exists(device_system_log.FullPath))
                {
                    main_log.WriteLine("A capture of the device log is: {0}", device_system_log.FullPath);
                    Harness.LogWrench("@MonkeyWrench: AddFile: {0}", device_system_log.FullPath);
                }
            }

            listener.Dispose();

            // check the final status
            var crashed = false;

            if (File.Exists(listener_log.FullPath))
            {
                Harness.LogWrench("@MonkeyWrench: AddFile: {0}", listener_log.FullPath);
                success = TestsSucceeded(listener_log, timed_out, crashed);
            }
            else if (timed_out)
            {
                Harness.LogWrench("@MonkeyWrench: AddSummary: <b><i>{0} never launched</i></b><br/>", mode);
                main_log.WriteLine("Test run never launched");
                success = false;
            }
            else if (launch_failure)
            {
                Harness.LogWrench("@MonkeyWrench: AddSummary: <b><i>{0} failed to launch</i></b><br/>", mode);
                main_log.WriteLine("Test run failed to launch");
                success = false;
            }
            else
            {
                Harness.LogWrench("@MonkeyWrench: AddSummary: <b><i>{0} crashed at startup (no log)</i></b><br/>", mode);
                main_log.WriteLine("Test run crashed before it started (no log file produced)");
                crashed = true;
                success = false;
            }

            if (!success.HasValue)
            {
                success = false;
            }

            await crash_reports.EndCaptureAsync(TimeSpan.FromSeconds(success.Value ? 0 : 5));

            if (timed_out)
            {
                Result = TestExecutingResult.TimedOut;
            }
            else if (crashed)
            {
                Result = TestExecutingResult.Crashed;
            }
            else if (success.Value)
            {
                Result = TestExecutingResult.Succeeded;
            }
            else
            {
                Result = TestExecutingResult.Failed;
            }
            return(success.Value ? 0 : 1);
        }
Esempio n. 7
0
        public static Task KillEverythingAsync(Log log)
        {
            var to_kill = new string [] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService" };

            return(ProcessHelper.ExecuteCommandAsync("killall", "-9 " + string.Join(" ", to_kill.Select((v) => Harness.Quote(v)).ToArray()), log, TimeSpan.FromSeconds(10)));
        }
        public static void ResolveAllPaths(this XmlDocument csproj, string project_path)
        {
            var dir = System.IO.Path.GetDirectoryName(project_path);
            var nodes_with_paths = new string []
            {
                "AssemblyOriginatorKeyFile",
                "CodesignEntitlements",
                "TestLibrariesDirectory",
                "HintPath",
            };
            var attributes_with_paths = new string [] []
            {
                new string [] { "None", "Include" },
                new string [] { "Compile", "Include" },
                new string [] { "ProjectReference", "Include" },
                new string [] { "InterfaceDefinition", "Include" },
                new string [] { "BundleResource", "Include" },
                new string [] { "EmbeddedResource", "Include" },
                new string [] { "ImageAsset", "Include" },
                new string [] { "GeneratedTestInput", "Include" },
                new string [] { "GeneratedTestOutput", "Include" },
                new string [] { "TestLibrariesInput", "Include" },
                new string [] { "TestLibrariesOutput", "Include" },
                new string [] { "TestLibrariesOutput", "Include" },
                new string [] { "Content", "Include" },
                new string [] { "ObjcBindingApiDefinition", "Include" },
                new string [] { "ObjcBindingCoreSource", "Include" },
                new string [] { "ObjcBindingNativeLibrary", "Include" },
                new string [] { "ObjcBindingNativeFramework", "Include" },
            };
            var nodes_with_variables = new string []
            {
                "MtouchExtraArgs",
            };
            Func <string, string> convert = (input) =>
            {
                if (input [0] == '/')
                {
                    return(input);                    // This is already a full path.
                }
                input = input.Replace('\\', '/');     // make unix-style
                input = System.IO.Path.GetFullPath(System.IO.Path.Combine(dir, input));
                input = input.Replace('/', '\\');     // make windows-style again
                return(input);
            };

            foreach (var key in nodes_with_paths)
            {
                var nodes = csproj.SelectElementNodes(key);
                foreach (var node in nodes)
                {
                    node.InnerText = convert(node.InnerText);
                }
            }
            foreach (var key in nodes_with_variables)
            {
                var nodes = csproj.SelectElementNodes(key);
                foreach (var node in nodes)
                {
                    node.InnerText = node.InnerText.Replace("${ProjectDir}", Harness.Quote(System.IO.Path.GetDirectoryName(project_path)));
                }
            }
            foreach (var kvp in attributes_with_paths)
            {
                var element = kvp [0];
                var attrib  = kvp [1];
                var nodes   = csproj.SelectElementNodes(element);
                foreach (XmlNode node in nodes)
                {
                    var a = node.Attributes [attrib];
                    if (a == null)
                    {
                        continue;
                    }
                    // Fix any default LogicalName values (but don't change existing ones).
                    var ln    = node.SelectElementNodes("LogicalName")?.SingleOrDefault();
                    var links = node.SelectElementNodes("Link");
                    if (ln == null && !links.Any())
                    {
                        ln = csproj.CreateElement("LogicalName", MSBuild_Namespace);
                        node.AppendChild(ln);

                        string logicalName = a.Value;
                        switch (element)
                        {
                        case "BundleResource":
                            if (logicalName.StartsWith("Resources\\", StringComparison.Ordinal))
                            {
                                logicalName = logicalName.Substring("Resources\\".Length);
                            }
                            break;

                        default:
                            break;
                        }
                        ln.InnerText = logicalName;
                    }

                    a.Value = convert(a.Value);
                }
            }
        }