diff --git a/src/OneScript.StandardLibrary/CodeStatisticsCollector.cs b/src/OneScript.StandardLibrary/CodeStatisticsCollector.cs
new file mode 100644
index 000000000..df42602d1
--- /dev/null
+++ b/src/OneScript.StandardLibrary/CodeStatisticsCollector.cs
@@ -0,0 +1,106 @@
+/*----------------------------------------------------------
+This Source Code Form is subject to the terms of the
+Mozilla Public License, v.2.0. If a copy of the MPL
+was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+----------------------------------------------------------*/
+
+using System.Linq;
+using OneScript.Contexts;
+using OneScript.Exceptions;
+using OneScript.StandardLibrary.Collections.ValueTable;
+using ScriptEngine.Machine;
+using ScriptEngine.Machine.Contexts;
+
+namespace OneScript.StandardLibrary
+{
+ ///
+ /// Сессия сбора статистики исполнения кода. Создаётся через СборСтатистики.НачатьСбор().
+ ///
+ [ContextClass("СборщикСтатистикиКода", "CodeStatisticsCollector")]
+ public sealed class CodeStatisticsCollector : AutoContext
+ {
+ private enum SessionState
+ {
+ Active,
+ Paused,
+ Finished
+ }
+
+ private readonly CodeStatHub _hub;
+ private readonly CodeStatProcessor _session;
+ private SessionState _state = SessionState.Active;
+
+ internal CodeStatisticsCollector(CodeStatHub hub, CodeStatProcessor session)
+ {
+ _hub = hub;
+ _session = session;
+ }
+
+ [ContextMethod("Приостановить", "Pause")]
+ public void Pause()
+ {
+ EnsureState(SessionState.Active);
+ _hub.PauseSession(_session);
+ _state = SessionState.Paused;
+ }
+
+ [ContextMethod("Восстановить", "Resume")]
+ public void Resume()
+ {
+ EnsureState(SessionState.Paused);
+ _hub.ResumeSession(_session);
+ _state = SessionState.Active;
+ }
+
+ /// Истина — не включать в результат строки с нулевым количеством выполнений
+ [ContextMethod("Завершить", "Finish")]
+ public ValueTable Finish(bool excludeZeros = false)
+ {
+ if (_state == SessionState.Finished)
+ ThrowInvalidState();
+
+ _hub.FinishSession(_session, excludeZeros);
+ _state = SessionState.Finished;
+ return ToValueTable(_session.GetStatData(excludeZeros));
+ }
+
+ private void EnsureState(SessionState expected)
+ {
+ if (_state != expected)
+ ThrowInvalidState();
+ }
+
+ private static void ThrowInvalidState()
+ {
+ throw new RuntimeException(
+ "Неверное состояние сборщика статистики кода",
+ "Invalid code statistics collector state");
+ }
+
+ private static ValueTable ToValueTable(CodeStatDataCollection data)
+ {
+ var table = new ValueTable();
+ var pathColumn = table.Columns.Add("Путь");
+ var methodColumn = table.Columns.Add("Метод");
+ var lineColumn = table.Columns.Add("НомерСтроки");
+ var countColumn = table.Columns.Add("Количество");
+ var timeColumn = table.Columns.Add("Время");
+
+ foreach (var item in data
+ .OrderBy(x => x.Entry.ScriptFileName)
+ .ThenBy(x => x.Entry.SubName)
+ .ThenBy(x => x.Entry.LineNumber))
+ {
+ var row = table.Add();
+ row.Set(pathColumn, ValueFactory.Create(item.Entry.ScriptFileName ?? string.Empty));
+ row.Set(methodColumn, ValueFactory.Create(item.Entry.SubName ?? string.Empty));
+ row.Set(lineColumn, ValueFactory.Create(item.Entry.LineNumber));
+ row.Set(countColumn, ValueFactory.Create(item.ExecutionCount));
+ row.Set(timeColumn, ValueFactory.Create((decimal)item.TimeElapsed));
+ }
+
+ return table;
+ }
+ }
+}
diff --git a/src/OneScript.StandardLibrary/CodeStatisticsContext.cs b/src/OneScript.StandardLibrary/CodeStatisticsContext.cs
new file mode 100644
index 000000000..fb5f6c2ad
--- /dev/null
+++ b/src/OneScript.StandardLibrary/CodeStatisticsContext.cs
@@ -0,0 +1,42 @@
+/*----------------------------------------------------------
+This Source Code Form is subject to the terms of the
+Mozilla Public License, v.2.0. If a copy of the MPL
+was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+----------------------------------------------------------*/
+
+using OneScript.Contexts;
+using OneScript.Exceptions;
+using OneScript.Execution;
+using ScriptEngine.Machine;
+using ScriptEngine.Machine.Contexts;
+
+namespace OneScript.StandardLibrary
+{
+ ///
+ /// Глобальный менеджер сбора статистики исполнения кода.
+ ///
+ [ContextClass("СборСтатистики", "CodeStatistics")]
+ public sealed class CodeStatisticsContext : AutoContext
+ {
+ [ContextMethod("СборДоступен", "CollectionAvailable")]
+ public bool CollectionAvailable(IBslProcess process)
+ {
+ return process.Services.TryResolve() != null;
+ }
+
+ [ContextMethod("НачатьСбор", "StartCollection")]
+ public CodeStatisticsCollector StartCollection(IBslProcess process)
+ {
+ if (process.Services.TryResolve() is not CodeStatHub hub)
+ {
+ throw new RuntimeException(
+ "Сбор статистики кода не включён. Запустите приложение с параметром -codestat",
+ "Code statistics collection is not enabled. Start the application with the -codestat switch");
+ }
+
+ var session = hub.StartSession();
+ return new CodeStatisticsCollector(hub, session);
+ }
+ }
+}
diff --git a/src/ScriptEngine.HostedScript/HostedScriptEngine.cs b/src/ScriptEngine.HostedScript/HostedScriptEngine.cs
index 6ed02ed64..bc0b05319 100644
--- a/src/ScriptEngine.HostedScript/HostedScriptEngine.cs
+++ b/src/ScriptEngine.HostedScript/HostedScriptEngine.cs
@@ -53,6 +53,9 @@ private void SetGlobalContexts(IGlobalsManager manager)
var bgTasksManager = new BackgroundTasksManager(_engine.Services.Resolve());
_env.InjectGlobalProperty(bgTasksManager, "ФоновыеЗадания", "BackgroundJobs", true);
+
+ var codeStatistics = new CodeStatisticsContext();
+ _env.InjectGlobalProperty(codeStatistics, "СборСтатистики", "CodeStatistics", true);
}
public void Initialize()
diff --git a/src/ScriptEngine/Machine/CodeStat/CodeStatHub.cs b/src/ScriptEngine/Machine/CodeStat/CodeStatHub.cs
new file mode 100644
index 000000000..416e2f8e5
--- /dev/null
+++ b/src/ScriptEngine/Machine/CodeStat/CodeStatHub.cs
@@ -0,0 +1,159 @@
+/*----------------------------------------------------------
+This Source Code Form is subject to the terms of the
+Mozilla Public License, v.2.0. If a copy of the MPL
+was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+----------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+
+namespace ScriptEngine.Machine
+{
+ public sealed class CodeStatHub : ICodeStatCollector
+ {
+ private readonly object _lock = new object();
+ private readonly List _knownEntries = new List();
+ private readonly HashSet _knownSet = new HashSet();
+ private readonly HashSet _preparedScripts = new HashSet();
+
+ private CodeStatProcessor[] _alive = Array.Empty();
+ private CodeStatProcessor[] _active = Array.Empty();
+
+ public CodeStatProcessor StartSession()
+ {
+ var session = new CodeStatProcessor(this);
+ lock (_lock)
+ {
+ _alive = Append(_alive, session);
+ _active = Append(_active, session);
+ }
+
+ return session;
+ }
+
+ public void PauseSession(CodeStatProcessor session)
+ {
+ lock (_lock)
+ {
+ session.StopActiveWatch();
+ _active = Remove(_active, session);
+ }
+ }
+
+ public void ResumeSession(CodeStatProcessor session)
+ {
+ lock (_lock)
+ {
+ if (Array.IndexOf(_alive, session) < 0)
+ return;
+ if (Array.IndexOf(_active, session) >= 0)
+ return;
+ _active = Append(_active, session);
+ }
+ }
+
+ public void FinishSession(CodeStatProcessor session, bool excludeZeros = false)
+ {
+ lock (_lock)
+ {
+ session.EndCodeStat();
+ if (excludeZeros)
+ session.FinishWithoutCatalog();
+ else
+ session.FreezeCatalog(_knownEntries.ToArray(), new HashSet(_preparedScripts));
+ _active = Remove(_active, session);
+ _alive = Remove(_alive, session);
+ }
+ }
+
+ internal CodeStatDataCollection GetLiveStatData(CodeStatProcessor session)
+ {
+ lock (_lock)
+ {
+ return session.BuildFromCatalog(_knownEntries, _knownEntries.Count, _preparedScripts);
+ }
+ }
+
+ public bool IsPrepared(string ScriptFileName)
+ {
+ lock (_lock)
+ {
+ return _preparedScripts.Contains(ScriptFileName);
+ }
+ }
+
+ internal HashSet SnapshotPreparedScripts()
+ {
+ lock (_lock)
+ {
+ return new HashSet(_preparedScripts);
+ }
+ }
+
+ public void MarkEntryReached(CodeStatEntry entry, int count = 1)
+ {
+ lock (_lock)
+ {
+ if (_knownSet.Add(entry))
+ _knownEntries.Add(entry);
+
+ if (count == 0)
+ return;
+
+ foreach (var session in _active)
+ session.MarkEntryReached(entry, count);
+ }
+ }
+
+ public void MarkPrepared(string scriptFileName)
+ {
+ lock (_lock)
+ {
+ _preparedScripts.Add(scriptFileName);
+ }
+ }
+
+ public void StopWatch(CodeStatEntry entry)
+ {
+ lock (_lock)
+ {
+ foreach (var session in _active)
+ session.StopWatch(entry);
+ }
+ }
+
+ public void ResumeWatch(CodeStatEntry entry)
+ {
+ lock (_lock)
+ {
+ foreach (var session in _active)
+ session.ResumeWatch(entry);
+ }
+ }
+
+ private static T[] Append(T[] source, T item)
+ {
+ var result = new T[source.Length + 1];
+ Array.Copy(source, result, source.Length);
+ result[source.Length] = item;
+ return result;
+ }
+
+ private static T[] Remove(T[] source, T item) where T : class
+ {
+ var index = Array.IndexOf(source, item);
+ if (index < 0)
+ return source;
+ if (source.Length == 1)
+ return Array.Empty();
+
+ var result = new T[source.Length - 1];
+ if (index > 0)
+ Array.Copy(source, 0, result, 0, index);
+ if (index < source.Length - 1)
+ Array.Copy(source, index + 1, result, index, source.Length - index - 1);
+ return result;
+ }
+ }
+}
diff --git a/src/ScriptEngine/Machine/CodeStat/CodeStatProcessor.cs b/src/ScriptEngine/Machine/CodeStat/CodeStatProcessor.cs
index 15be6508f..d0cb1c526 100644
--- a/src/ScriptEngine/Machine/CodeStat/CodeStatProcessor.cs
+++ b/src/ScriptEngine/Machine/CodeStat/CodeStatProcessor.cs
@@ -1,21 +1,36 @@
/*----------------------------------------------------------
-This Source Code Form is subject to the terms of the
-Mozilla Public License, v.2.0. If a copy of the MPL
-was not distributed with this file, You can obtain one
+This Source Code Form is subject to the terms of the
+Mozilla Public License, v.2.0. If a copy of the MPL
+was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.
----------------------------------------------------------*/
-using System.Diagnostics;
using System.Collections.Generic;
+using System.Diagnostics;
namespace ScriptEngine.Machine
{
public class CodeStatProcessor : ICodeStatCollector
{
- private Dictionary _codeStat = new Dictionary();
- private Dictionary _watchers = new Dictionary();
- private Stopwatch _activeStopwatch = null;
- private HashSet _preparedScripts = new HashSet();
+ private readonly CodeStatHub _hub;
+ private readonly Dictionary _codeStat = new Dictionary();
+ private readonly Dictionary _watchers = new Dictionary();
+ private readonly HashSet _preparedScripts = new HashSet();
+
+ private Stopwatch _activeStopwatch;
+ private IReadOnlyList _frozenEntries;
+ private int _frozenCount;
+ private HashSet _frozenPrepared;
+ private bool _finishedHitsOnly;
+
+ public CodeStatProcessor()
+ {
+ }
+
+ internal CodeStatProcessor(CodeStatHub hub)
+ {
+ _hub = hub;
+ }
public bool IsPrepared(string ScriptFileName)
{
@@ -24,23 +39,21 @@ public bool IsPrepared(string ScriptFileName)
public void MarkEntryReached(CodeStatEntry entry, int count = 1)
{
- int oldValue = 0;
- _codeStat.TryGetValue(entry, out oldValue);
+ _codeStat.TryGetValue(entry, out var oldValue);
_codeStat[entry] = oldValue + count;
if (count == 0)
+ return;
+
+ _activeStopwatch?.Stop();
+ if (!_watchers.TryGetValue(entry, out var watch))
{
- if (!_watchers.ContainsKey(entry))
- {
- _watchers.Add(entry, new Stopwatch());
- }
- }
- else
- {
- _activeStopwatch?.Stop();
- _activeStopwatch = _watchers[entry];
- _activeStopwatch.Start();
+ watch = new Stopwatch();
+ _watchers[entry] = watch;
}
+
+ _activeStopwatch = watch;
+ _activeStopwatch.Start();
}
public void MarkPrepared(string scriptFileName)
@@ -48,41 +61,108 @@ public void MarkPrepared(string scriptFileName)
_preparedScripts.Add(scriptFileName);
}
- public CodeStatDataCollection GetStatData()
+ public CodeStatDataCollection GetStatData(bool excludeZeros = false)
+ {
+ if (excludeZeros || _finishedHitsOnly)
+ return BuildFromHits(requirePrepared: true, prepared: PreparedScriptsForHits());
+
+ if (_frozenEntries != null)
+ return BuildFromCatalog(_frozenEntries, _frozenCount, _frozenPrepared);
+
+ if (_hub != null)
+ return _hub.GetLiveStatData(this);
+
+ return BuildFromHits(requirePrepared: true, includeZeros: true);
+ }
+
+ private HashSet PreparedScriptsForHits()
+ {
+ return _hub != null ? _hub.SnapshotPreparedScripts() : _preparedScripts;
+ }
+
+ internal void FreezeCatalog(CodeStatEntry[] entries, HashSet prepared)
{
- CodeStatDataCollection data = new CodeStatDataCollection();
+ _frozenEntries = entries;
+ _frozenCount = entries.Length;
+ _frozenPrepared = prepared;
+ }
+
+ internal void FinishWithoutCatalog()
+ {
+ _finishedHitsOnly = true;
+ }
+
+ internal CodeStatDataCollection BuildFromCatalog(
+ IReadOnlyList entries,
+ int count,
+ HashSet prepared)
+ {
+ var data = new CodeStatDataCollection();
+ for (var i = 0; i < count; i++)
+ {
+ var entry = entries[i];
+ if (!prepared.Contains(entry.ScriptFileName))
+ continue;
+
+ _codeStat.TryGetValue(entry, out var executionCount);
+ long time = 0;
+ if (_watchers.TryGetValue(entry, out var watch))
+ time = watch.ElapsedMilliseconds;
+
+ data.Add(new CodeStatData(entry, time, executionCount));
+ }
+
+ return data;
+ }
+
+ private CodeStatDataCollection BuildFromHits(
+ bool requirePrepared = false,
+ bool includeZeros = false,
+ HashSet prepared = null)
+ {
+ var data = new CodeStatDataCollection();
+ var preparedSet = prepared ?? _preparedScripts;
foreach (var item in _codeStat)
{
- if (!IsPrepared(item.Key.ScriptFileName))
- {
+ if (!includeZeros && item.Value == 0)
continue;
- }
- data.Add(new CodeStatData(item.Key, _watchers[item.Key].ElapsedMilliseconds, item.Value));
+ if (requirePrepared && !preparedSet.Contains(item.Key.ScriptFileName))
+ continue;
+
+ long time = 0;
+ if (_watchers.TryGetValue(item.Key, out var watch))
+ time = watch.ElapsedMilliseconds;
+
+ data.Add(new CodeStatData(item.Key, time, item.Value));
}
-
+
return data;
}
public void EndCodeStat()
+ {
+ StopActiveWatch();
+ }
+
+ public void StopActiveWatch()
{
_activeStopwatch?.Stop();
+ _activeStopwatch = null;
}
public void StopWatch(CodeStatEntry entry)
{
- if (_watchers.ContainsKey(entry))
- {
- _watchers[entry].Stop();
- }
+ if (_watchers.TryGetValue(entry, out var watch))
+ watch.Stop();
}
public void ResumeWatch(CodeStatEntry entry)
{
_activeStopwatch?.Stop();
- if (_watchers.ContainsKey(entry))
+ if (_watchers.TryGetValue(entry, out var watch))
{
- _activeStopwatch = _watchers[entry];
+ _activeStopwatch = watch;
_activeStopwatch.Start();
}
}
diff --git a/src/Tests/OneScript.Core.Tests/CodeStatHubTests.cs b/src/Tests/OneScript.Core.Tests/CodeStatHubTests.cs
new file mode 100644
index 000000000..d2def26f9
--- /dev/null
+++ b/src/Tests/OneScript.Core.Tests/CodeStatHubTests.cs
@@ -0,0 +1,313 @@
+/*----------------------------------------------------------
+This Source Code Form is subject to the terms of the
+Mozilla Public License, v.2.0. If a copy of the MPL
+was not distributed with this file, You can obtain one
+at http://mozilla.org/MPL/2.0/.
+----------------------------------------------------------*/
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentAssertions;
+using ScriptEngine.Machine;
+using Xunit;
+
+namespace OneScript.Core.Tests
+{
+ public class CodeStatHubTests
+ {
+ [Fact]
+ public void Hits_Go_To_Every_Active_Session()
+ {
+ var hub = new CodeStatHub();
+ var first = hub.StartSession();
+ var second = hub.StartSession();
+ var entry = new CodeStatEntry("script.os", "Method", 1);
+
+ hub.MarkEntryReached(entry, 0);
+ hub.MarkPrepared("script.os");
+ hub.MarkEntryReached(entry);
+
+ CountOf(first, entry).Should().Be(1);
+ CountOf(second, entry).Should().Be(1);
+ }
+
+ [Fact]
+ public void New_Session_Gets_Prepared_Zeros_Without_History()
+ {
+ var hub = new CodeStatHub();
+ var first = hub.StartSession();
+ var entry = new CodeStatEntry("script.os", "Method", 1);
+
+ hub.MarkEntryReached(entry, 0);
+ hub.MarkPrepared("script.os");
+ hub.MarkEntryReached(entry);
+ hub.MarkEntryReached(entry);
+
+ var second = hub.StartSession();
+
+ CountOf(first, entry).Should().Be(2);
+ CountOf(second, entry).Should().Be(0);
+
+ hub.MarkEntryReached(entry);
+
+ CountOf(first, entry).Should().Be(3);
+ CountOf(second, entry).Should().Be(1);
+ }
+
+ [Fact]
+ public void Paused_Session_Does_Not_Receive_Hits()
+ {
+ var hub = new CodeStatHub();
+ var session = hub.StartSession();
+ var entry = new CodeStatEntry("script.os", "Method", 1);
+
+ hub.MarkEntryReached(entry, 0);
+ hub.MarkPrepared("script.os");
+ hub.MarkEntryReached(entry);
+
+ hub.PauseSession(session);
+ hub.MarkEntryReached(entry);
+ hub.MarkEntryReached(entry);
+
+ CountOf(session, entry).Should().Be(1);
+
+ hub.ResumeSession(session);
+ hub.MarkEntryReached(entry);
+
+ CountOf(session, entry).Should().Be(2);
+ }
+
+ [Fact]
+ public void Paused_Session_Receives_Prepared_Zeros()
+ {
+ var hub = new CodeStatHub();
+ var session = hub.StartSession();
+ hub.PauseSession(session);
+
+ var entry = new CodeStatEntry("other.os", "Other", 2);
+ hub.MarkEntryReached(entry, 0);
+ hub.MarkPrepared("other.os");
+
+ CountOf(session, entry).Should().Be(0);
+ session.GetStatData().Should().Contain(x => x.Entry.Equals(entry));
+ }
+
+ [Fact]
+ public void Finished_Session_Does_Not_Receive_Further_Events()
+ {
+ var hub = new CodeStatHub();
+ var finished = hub.StartSession();
+ var alive = hub.StartSession();
+ var first = new CodeStatEntry("script.os", "Method", 1);
+
+ hub.MarkEntryReached(first, 0);
+ hub.MarkPrepared("script.os");
+ hub.MarkEntryReached(first);
+ hub.FinishSession(finished);
+
+ hub.MarkEntryReached(first);
+
+ var second = new CodeStatEntry("later.os", "Later", 3);
+ hub.MarkEntryReached(second, 0);
+ hub.MarkPrepared("later.os");
+
+ CountOf(finished, first).Should().Be(1);
+ finished.GetStatData().Should().NotContain(x => x.Entry.Equals(second));
+ CountOf(alive, first).Should().Be(2);
+ CountOf(alive, second).Should().Be(0);
+ }
+
+ [Fact]
+ public void StartSession_Reads_Zeros_From_Shared_Catalog()
+ {
+ var hub = new CodeStatHub();
+ for (var line = 1; line <= 50; line++)
+ {
+ hub.MarkEntryReached(new CodeStatEntry("script.os", "Method", line), 0);
+ }
+
+ hub.MarkPrepared("script.os");
+ var session = hub.StartSession();
+ var data = session.GetStatData();
+
+ data.Count.Should().Be(50);
+ data.Should().OnlyContain(x => x.ExecutionCount == 0 && x.TimeElapsed == 0);
+ }
+
+ [Fact]
+ public void GetStatData_ExcludeZeros_Ignores_Hits_Before_Prepared()
+ {
+ var hub = new CodeStatHub();
+ var session = hub.StartSession();
+ var early = new CodeStatEntry("early.os", "Method", 1);
+ var ready = new CodeStatEntry("ready.os", "Method", 1);
+
+ hub.MarkEntryReached(early);
+ hub.MarkPrepared("ready.os");
+ hub.MarkEntryReached(ready);
+
+ session.GetStatData().Should().NotContain(x => x.Entry.Equals(early));
+ session.GetStatData().Should().ContainSingle(x => x.Entry.Equals(ready) && x.ExecutionCount == 1);
+
+ var withoutZeros = session.GetStatData(true);
+ withoutZeros.Should().NotContain(x => x.Entry.Equals(early));
+ withoutZeros.Should().ContainSingle(x => x.Entry.Equals(ready) && x.ExecutionCount == 1);
+
+ hub.FinishSession(session, excludeZeros: true);
+ var finished = session.GetStatData(true);
+ finished.Should().NotContain(x => x.Entry.Equals(early));
+ finished.Should().ContainSingle(x => x.Entry.Equals(ready) && x.ExecutionCount == 1);
+ }
+
+ [Fact]
+ public void Standalone_GetStatData_ExcludeZeros_Ignores_Hits_Before_Prepared()
+ {
+ var processor = new CodeStatProcessor();
+ var early = new CodeStatEntry("early.os", "Method", 1);
+
+ processor.MarkEntryReached(early);
+
+ processor.GetStatData().Should().BeEmpty();
+ processor.GetStatData(true).Should().BeEmpty();
+
+ processor.MarkPrepared("early.os");
+ processor.GetStatData(true).Should().ContainSingle(x => x.Entry.Equals(early) && x.ExecutionCount == 1);
+ }
+
+ [Fact]
+ public void GetStatData_ExcludeZeros_Returns_Only_Hits()
+ {
+ var hub = new CodeStatHub();
+ var session = hub.StartSession();
+ var hit = new CodeStatEntry("script.os", "Method", 1);
+ var zero = new CodeStatEntry("script.os", "Method", 2);
+
+ hub.MarkEntryReached(hit, 0);
+ hub.MarkEntryReached(zero, 0);
+ hub.MarkPrepared("script.os");
+ hub.MarkEntryReached(hit);
+
+ var live = session.GetStatData(true);
+ live.Should().ContainSingle(x => x.Entry.Equals(hit) && x.ExecutionCount == 1);
+ live.Should().NotContain(x => x.Entry.Equals(zero));
+
+ hub.FinishSession(session, excludeZeros: true);
+ var finished = session.GetStatData(true);
+ finished.Should().ContainSingle(x => x.Entry.Equals(hit) && x.ExecutionCount == 1);
+ finished.Should().NotContain(x => x.Entry.Equals(zero));
+ }
+
+ [Fact]
+ public void ResumeSession_Does_Not_Revive_Finished_Session()
+ {
+ var hub = new CodeStatHub();
+ var session = hub.StartSession();
+ var entry = new CodeStatEntry("script.os", "Method", 1);
+
+ hub.MarkEntryReached(entry, 0);
+ hub.MarkPrepared("script.os");
+ hub.MarkEntryReached(entry);
+ hub.FinishSession(session);
+
+ hub.ResumeSession(session);
+ hub.MarkEntryReached(entry);
+ hub.MarkEntryReached(entry);
+
+ CountOf(session, entry).Should().Be(1);
+ }
+
+ [Fact]
+ public void Concurrent_ResumeWatch_Does_Not_Restart_After_Pause_Or_Finish()
+ {
+ AssertWatchDoesNotResumeAfterControl(pause: true);
+ AssertWatchDoesNotResumeAfterControl(pause: false);
+ }
+
+ [Fact]
+ public void Concurrent_Hits_Do_Not_Apply_After_Pause_Or_Finish()
+ {
+ AssertHitsDoNotApplyAfterControl(pause: true);
+ AssertHitsDoNotApplyAfterControl(pause: false);
+ }
+
+ private static void AssertWatchDoesNotResumeAfterControl(bool pause)
+ {
+ var hub = new CodeStatHub();
+ var session = hub.StartSession();
+ var entry = new CodeStatEntry("script.os", "Method", 1);
+ hub.MarkEntryReached(entry, 0);
+ hub.MarkPrepared("script.os");
+ hub.MarkEntryReached(entry);
+
+ using var stopResuming = new ManualResetEventSlim(false);
+ var start = new Barrier(2);
+
+ var resumer = Task.Run(() =>
+ {
+ start.SignalAndWait();
+ while (!stopResuming.IsSet)
+ hub.ResumeWatch(entry);
+ });
+
+ start.SignalAndWait();
+ if (pause)
+ hub.PauseSession(session);
+ else
+ hub.FinishSession(session);
+
+ var timeWhenControlReturned = TimeOf(session, entry);
+ Thread.Sleep(40);
+ stopResuming.Set();
+ resumer.Wait();
+
+ TimeOf(session, entry).Should().Be(timeWhenControlReturned);
+ }
+
+ private static void AssertHitsDoNotApplyAfterControl(bool pause)
+ {
+ var hub = new CodeStatHub();
+ var session = hub.StartSession();
+ var entry = new CodeStatEntry("script.os", "Method", 1);
+ hub.MarkEntryReached(entry, 0);
+ hub.MarkPrepared("script.os");
+
+ using var stopHitting = new ManualResetEventSlim(false);
+ var start = new Barrier(2);
+
+ var hitter = Task.Run(() =>
+ {
+ start.SignalAndWait();
+ while (!stopHitting.IsSet)
+ hub.MarkEntryReached(entry);
+ });
+
+ start.SignalAndWait();
+ if (pause)
+ hub.PauseSession(session);
+ else
+ hub.FinishSession(session);
+
+ var countWhenControlReturned = CountOf(session, entry);
+ var timeWhenControlReturned = TimeOf(session, entry);
+
+ Thread.Sleep(30);
+ stopHitting.Set();
+ hitter.Wait();
+
+ CountOf(session, entry).Should().Be(countWhenControlReturned);
+ TimeOf(session, entry).Should().Be(timeWhenControlReturned);
+ }
+
+ private static int CountOf(CodeStatProcessor session, CodeStatEntry entry)
+ {
+ return session.GetStatData().Single(x => x.Entry.Equals(entry)).ExecutionCount;
+ }
+
+ private static long TimeOf(CodeStatProcessor session, CodeStatEntry entry)
+ {
+ return session.GetStatData().Single(x => x.Entry.Equals(entry)).TimeElapsed;
+ }
+ }
+}
diff --git a/src/oscript/BehaviorSelector.cs b/src/oscript/BehaviorSelector.cs
index 264cfb64f..db6337ab1 100644
--- a/src/oscript/BehaviorSelector.cs
+++ b/src/oscript/BehaviorSelector.cs
@@ -60,14 +60,14 @@ private static AppBehavior SelectParametrized(CmdLineHelper helper)
private static AppBehavior EnableCodeStatistics(CmdLineHelper helper)
{
var param = helper.Parse(helper.Current());
- if (string.IsNullOrEmpty(param.Value))
- return null;
-
- var outputStatFile = param.Value;
+ var outputStatFile = string.IsNullOrEmpty(param.Value) ? null : param.Value;
var behavior = Select(helper.Tail());
if (behavior is ExecuteScriptBehavior executor)
+ {
+ executor.CodeStatisticsEnabled = true;
executor.CodeStatFile = outputStatFile;
+ }
return behavior;
}
diff --git a/src/oscript/ExecuteScriptBehavior.cs b/src/oscript/ExecuteScriptBehavior.cs
index 49cbc29c4..bca291ee6 100644
--- a/src/oscript/ExecuteScriptBehavior.cs
+++ b/src/oscript/ExecuteScriptBehavior.cs
@@ -29,7 +29,7 @@ public ExecuteScriptBehavior(string path, string[] args)
public string CodeStatFile { get; set; }
- public bool CodeStatisticsEnabled => CodeStatFile != null;
+ public bool CodeStatisticsEnabled { get; set; }
public override int Execute()
{
@@ -43,11 +43,15 @@ public override int Execute()
var builder = ConsoleHostBuilder.Create(_path);
builder.WithDebugger(DebugController);
- CodeStatProcessor codeStatProcessor = null;
+ CodeStatHub codeStatHub = null;
+ CodeStatProcessor cliSession = null;
if (CodeStatisticsEnabled)
{
- codeStatProcessor = new CodeStatProcessor();
- builder.Services.RegisterSingleton(codeStatProcessor);
+ codeStatHub = new CodeStatHub();
+ builder.Services.RegisterSingleton(codeStatHub);
+ builder.Services.RegisterSingleton(codeStatHub);
+ if (CodeStatFile != null)
+ cliSession = codeStatHub.StartSession();
}
var hostedScript = ConsoleHostBuilder.Build(builder);
@@ -67,12 +71,11 @@ public override int Execute()
var result = process.Start();
hostedScript.Dispose();
- if (codeStatProcessor != null)
+ if (cliSession != null && codeStatHub != null)
{
- codeStatProcessor.EndCodeStat();
- var codeStat = codeStatProcessor.GetStatData();
+ codeStatHub.FinishSession(cliSession);
var statsWriter = new CodeStatWriter(CodeStatFile, CodeStatWriterType.JSON);
- statsWriter.Write(codeStat);
+ statsWriter.Write(cliSession.GetStatData());
}
return result;
diff --git a/src/oscript/ShowUsageBehavior.cs b/src/oscript/ShowUsageBehavior.cs
index ee1c26a57..ec652dabd 100644
--- a/src/oscript/ShowUsageBehavior.cs
+++ b/src/oscript/ShowUsageBehavior.cs
@@ -39,7 +39,7 @@ public override int Execute()
Output.WriteLine("Options:");
Output.WriteLine($" {"-encoding=",modeWidth} Set output encoding (e.g. utf-8).");
- Output.WriteLine($" {"-codestat=",modeWidth} Write code execution statistics to file.");
+ Output.WriteLine($" {"-codestat[=]",modeWidth} Enable code statistics; write to file if specified.");
Output.WriteLine();
Output.WriteLine("CGI Mode:");
diff --git a/tests/codestat-api.os b/tests/codestat-api.os
new file mode 100644
index 000000000..151354d4c
--- /dev/null
+++ b/tests/codestat-api.os
@@ -0,0 +1,412 @@
+///////////////////////////////////////////////////////////////////////
+//
+// Сбор статистики из исполняемого кода
+//
+///////////////////////////////////////////////////////////////////////
+
+Перем юТест;
+
+Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт
+
+ юТест = ЮнитТестирование;
+
+ ВсеТесты = Новый Массив;
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоСборСтатистикиЕстьВГлобальномКонтексте");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоСборНедоступенБезФлага");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоНачатьСборБросаетБезФлага");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьСборДоступенИРезультатЗавершения");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьПересекающиесяСборы");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьПаузуИВосстановление");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьПереходыСостояний");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьИсключениеНулевыхСтрок");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьДампВФайл");
+
+ Возврат ВсеТесты;
+
+КонецФункции
+
+Процедура ТестДолжен_ПроверитьЧтоСборСтатистикиЕстьВГлобальномКонтексте() Экспорт
+
+ юТест.ПроверитьРавенство(Тип("СборСтатистики"), ТипЗнч(СборСтатистики));
+ юТест.ПроверитьЛожь(СборСтатистики.СборДоступен());
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьЧтоСборНедоступенБезФлага() Экспорт
+
+ юТест.ПроверитьЛожь(СборСтатистики.СборДоступен(), "Без -codestat сбор должен быть недоступен");
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьЧтоНачатьСборБросаетБезФлага() Экспорт
+
+ Бросило = Ложь;
+ Попытка
+ СборСтатистики.НачатьСбор();
+ Исключение
+ Бросило = Истина;
+ юТест.ПроверитьИстину(
+ СтрНайти(ИнформацияОбОшибке().Описание, "-codestat") > 0,
+ "Текст исключения должен упоминать -codestat"
+ );
+ КонецПопытки;
+
+ юТест.ПроверитьИстину(Бросило, "НачатьСбор без -codestat должен выбрасывать исключение");
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьСборДоступенИРезультатЗавершения() Экспорт
+
+ ТекстСкрипта =
+ "Процедура ВыполняемыйМетод()
+ | Значение = 1;
+ |КонецПроцедуры
+ |
+ |Процедура НевызываемыйМетод()
+ | Значение = 2;
+ |КонецПроцедуры
+ |
+ |Функция КоличествоХитов(Таблица, ИмяМетода)
+ | Сумма = 0;
+ | Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Метод = ИмяМетода Тогда
+ | Сумма = Сумма + СтрокаТаблицы.Количество;
+ | КонецЕсли;
+ | КонецЦикла;
+ | Возврат Сумма;
+ |КонецФункции
+ |
+ |Функция ВсеСтрокиМетодаСНулями(Таблица, ИмяМетода)
+ | Нашли = Ложь;
+ | Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Метод = ИмяМетода Тогда
+ | Нашли = Истина;
+ | Если СтрокаТаблицы.Количество <> 0 Тогда
+ | Возврат Ложь;
+ | КонецЕсли;
+ | КонецЕсли;
+ | КонецЦикла;
+ | Возврат Нашли;
+ |КонецФункции
+ |
+ |Если Не СборСтатистики.СборДоступен() Тогда
+ | ВызватьИсключение ""Сбор должен быть доступен"";
+ |КонецЕсли;
+ |
+ |Сбор = СборСтатистики.НачатьСбор();
+ |ВыполняемыйМетод();
+ |Таблица = Сбор.Завершить();
+ |
+ |Если ТипЗнч(Таблица) <> Тип(""ТаблицаЗначений"") Тогда
+ | ВызватьИсключение ""Завершить должен вернуть ТаблицаЗначений"";
+ |КонецЕсли;
+ |
+ |Для Каждого ИмяКолонки Из СтрРазделить(""Путь,Метод,НомерСтроки,Количество,Время"", "","") Цикл
+ | Если Таблица.Колонки.Найти(ИмяКолонки) = Неопределено Тогда
+ | ВызватьИсключение ""Нет колонки "" + ИмяКолонки;
+ | КонецЕсли;
+ |КонецЦикла;
+ |
+ |Если КоличествоХитов(Таблица, ""ВыполняемыйМетод"") = 0 Тогда
+ | ВызватьИсключение ""В результате должны быть хиты ВыполняемыйМетод"";
+ |КонецЕсли;
+ |
+ |Если Не ВсеСтрокиМетодаСНулями(Таблица, ""НевызываемыйМетод"") Тогда
+ | ВызватьИсключение ""Завершить должен вернуть непройденные строки НевызываемыйМетод с Количество = 0"";
+ |КонецЕсли;";
+
+ ПроверитьУспешныйЗапуск(ТекстСкрипта);
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьИсключениеНулевыхСтрок() Экспорт
+
+ ТекстСкрипта =
+ "Процедура ВыполняемыйМетод()
+ | Значение = 1;
+ |КонецПроцедуры
+ |
+ |Процедура НевызываемыйМетод()
+ | Значение = 2;
+ |КонецПроцедуры
+ |
+ |Функция КоличествоХитов(Таблица, ИмяМетода)
+ | Сумма = 0;
+ | Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Метод = ИмяМетода Тогда
+ | Сумма = Сумма + СтрокаТаблицы.Количество;
+ | КонецЕсли;
+ | КонецЦикла;
+ | Возврат Сумма;
+ |КонецФункции
+ |
+ |Функция ЕстьСтрокиМетода(Таблица, ИмяМетода)
+ | Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Метод = ИмяМетода Тогда
+ | Возврат Истина;
+ | КонецЕсли;
+ | КонецЦикла;
+ | Возврат Ложь;
+ |КонецФункции
+ |
+ |Сбор = СборСтатистики.НачатьСбор();
+ |ВыполняемыйМетод();
+ |Таблица = Сбор.Завершить(Истина);
+ |
+ |Если КоличествоХитов(Таблица, ""ВыполняемыйМетод"") = 0 Тогда
+ | ВызватьИсключение ""Хиты ВыполняемыйМетод должны остаться"";
+ |КонецЕсли;
+ |
+ |Если ЕстьСтрокиМетода(Таблица, ""НевызываемыйМетод"") Тогда
+ | ВызватьИсключение ""НевызываемыйМетод не должен попасть в таблицу при excludeZeros"";
+ |КонецЕсли;
+ |
+ |Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Количество = 0 Тогда
+ | ВызватьИсключение ""В таблице не должно быть строк с Количество = 0"";
+ | КонецЕсли;
+ |КонецЦикла;";
+
+ ПроверитьУспешныйЗапуск(ТекстСкрипта);
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьПересекающиесяСборы() Экспорт
+
+ ТекстСкрипта =
+ "Процедура ТолькоА()
+ | Значение = 1;
+ |КонецПроцедуры
+ |
+ |Процедура ОбщийКод()
+ | Значение = 2;
+ |КонецПроцедуры
+ |
+ |Функция КоличествоХитов(Таблица, ИмяМетода)
+ | Сумма = 0;
+ | Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Метод = ИмяМетода Тогда
+ | Сумма = Сумма + СтрокаТаблицы.Количество;
+ | КонецЕсли;
+ | КонецЦикла;
+ | Возврат Сумма;
+ |КонецФункции
+ |
+ |Функция ВсеСтрокиМетодаСНулями(Таблица, ИмяМетода)
+ | Нашли = Ложь;
+ | Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Метод = ИмяМетода Тогда
+ | Нашли = Истина;
+ | Если СтрокаТаблицы.Количество <> 0 Тогда
+ | Возврат Ложь;
+ | КонецЕсли;
+ | КонецЕсли;
+ | КонецЦикла;
+ | Возврат Нашли;
+ |КонецФункции
+ |
+ |СборА = СборСтатистики.НачатьСбор();
+ |ТолькоА();
+ |
+ |СборБ = СборСтатистики.НачатьСбор();
+ |ОбщийКод();
+ |
+ |ТаблицаБ = СборБ.Завершить();
+ |ТолькоА();
+ |ТаблицаА = СборА.Завершить();
+ |
+ |Если КоличествоХитов(ТаблицаА, ""ТолькоА"") = 0 Тогда
+ | ВызватьИсключение ""Сбор А должен содержать ТолькоА"";
+ |КонецЕсли;
+ |Если Не ВсеСтрокиМетодаСНулями(ТаблицаБ, ""ТолькоА"") Тогда
+ | ВызватьИсключение ""Сбор Б должен содержать непройденные строки ТолькоА с Количество = 0"";
+ |КонецЕсли;
+ |Если КоличествоХитов(ТаблицаА, ""ОбщийКод"") = 0 Тогда
+ | ВызватьИсключение ""Сбор А должен содержать ОбщийКод"";
+ |КонецЕсли;
+ |Если КоличествоХитов(ТаблицаБ, ""ОбщийКод"") = 0 Тогда
+ | ВызватьИсключение ""Сбор Б должен содержать ОбщийКод"";
+ |КонецЕсли;";
+
+ ПроверитьУспешныйЗапуск(ТекстСкрипта);
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьПаузуИВосстановление() Экспорт
+
+ ТекстСкрипта =
+ "Процедура ДоПаузы()
+ | Значение = 1;
+ |КонецПроцедуры
+ |
+ |Процедура НаПаузе()
+ | Значение = 2;
+ |КонецПроцедуры
+ |
+ |Процедура ПослеПаузы()
+ | Значение = 3;
+ |КонецПроцедуры
+ |
+ |Функция КоличествоХитов(Таблица, ИмяМетода)
+ | Сумма = 0;
+ | Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Метод = ИмяМетода Тогда
+ | Сумма = Сумма + СтрокаТаблицы.Количество;
+ | КонецЕсли;
+ | КонецЦикла;
+ | Возврат Сумма;
+ |КонецФункции
+ |
+ |Функция ВсеСтрокиМетодаСНулями(Таблица, ИмяМетода)
+ | Нашли = Ложь;
+ | Для Каждого СтрокаТаблицы Из Таблица Цикл
+ | Если СтрокаТаблицы.Метод = ИмяМетода Тогда
+ | Нашли = Истина;
+ | Если СтрокаТаблицы.Количество <> 0 Тогда
+ | Возврат Ложь;
+ | КонецЕсли;
+ | КонецЕсли;
+ | КонецЦикла;
+ | Возврат Нашли;
+ |КонецФункции
+ |
+ |Сбор = СборСтатистики.НачатьСбор();
+ |ДоПаузы();
+ |Сбор.Приостановить();
+ |НаПаузе();
+ |Сбор.Восстановить();
+ |ПослеПаузы();
+ |Таблица = Сбор.Завершить();
+ |
+ |Если КоличествоХитов(Таблица, ""ДоПаузы"") = 0 Тогда
+ | ВызватьИсключение ""Должны быть хиты ДоПаузы"";
+ |КонецЕсли;
+ |Если Не ВсеСтрокиМетодаСНулями(Таблица, ""НаПаузе"") Тогда
+ | ВызватьИсключение ""Завершить должен вернуть строки НаПаузе с Количество = 0"";
+ |КонецЕсли;
+ |Если КоличествоХитов(Таблица, ""ПослеПаузы"") = 0 Тогда
+ | ВызватьИсключение ""Должны быть хиты ПослеПаузы"";
+ |КонецЕсли;";
+
+ ПроверитьУспешныйЗапуск(ТекстСкрипта);
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьПереходыСостояний() Экспорт
+
+ ТекстСкрипта =
+ "Процедура ОжидатьИсключение(Сборщик, ИмяМетода, Описание)
+ | Бросило = Ложь;
+ | Попытка
+ | Если ИмяМетода = ""Восстановить"" Тогда
+ | Сборщик.Восстановить();
+ | ИначеЕсли ИмяМетода = ""Приостановить"" Тогда
+ | Сборщик.Приостановить();
+ | ИначеЕсли ИмяМетода = ""Завершить"" Тогда
+ | Сборщик.Завершить();
+ | КонецЕсли;
+ | Исключение
+ | Бросило = Истина;
+ | Если СтрНайти(ИнформацияОбОшибке().Описание, ""Неверное состояние"") = 0 Тогда
+ | ВызватьИсключение;
+ | КонецЕсли;
+ | КонецПопытки;
+ | Если Не Бросило Тогда
+ | ВызватьИсключение Описание + "": ожидалось исключение"";
+ | КонецЕсли;
+ |КонецПроцедуры
+ |
+ |Сбор = СборСтатистики.НачатьСбор();
+ |ОжидатьИсключение(Сбор, ""Восстановить"", ""Восстановить из Active"");
+ |
+ |Сбор.Приостановить();
+ |ОжидатьИсключение(Сбор, ""Приостановить"", ""Повторная пауза"");
+ |
+ |Сбор.Восстановить();
+ |Таблица = Сбор.Завершить();
+ |Если ТипЗнч(Таблица) <> Тип(""ТаблицаЗначений"") Тогда
+ | ВызватьИсключение ""Завершить из Active должен вернуть таблицу"";
+ |КонецЕсли;
+ |
+ |ОжидатьИсключение(Сбор, ""Завершить"", ""Повторный Завершить"");
+ |ОжидатьИсключение(Сбор, ""Приостановить"", ""Пауза после Завершить"");
+ |ОжидатьИсключение(Сбор, ""Восстановить"", ""Восстановить после Завершить"");
+ |
+ |СборСПаузы = СборСтатистики.НачатьСбор();
+ |СборСПаузы.Приостановить();
+ |ТаблицаСПаузы = СборСПаузы.Завершить();
+ |Если ТипЗнч(ТаблицаСПаузы) <> Тип(""ТаблицаЗначений"") Тогда
+ | ВызватьИсключение ""Завершить из Paused должен вернуть таблицу"";
+ |КонецЕсли;";
+
+ ПроверитьУспешныйЗапуск(ТекстСкрипта);
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьДампВФайл() Экспорт
+
+ ИмяФайлаСкрипта = ПолучитьИмяВременногоФайла("os");
+ ИмяФайлаСтатистики = ПолучитьИмяВременногоФайла("json");
+
+ Запись = Новый ЗаписьТекста(ИмяФайлаСкрипта, КодировкаТекста.UTF8);
+ Запись.Записать("Значение = 1;");
+ Запись.Закрыть();
+
+ Попытка
+ Результат = ЗапуститьОскрипт("-codestat=" + ИмяФайлаСтатистики + " """ + ИмяФайлаСкрипта + """");
+ юТест.ПроверитьРавенство(0, Результат.КодВозврата, Результат.Вывод);
+
+ ФайлСтатистики = Новый Файл(ИмяФайлаСтатистики);
+ юТест.ПроверитьИстину(ФайлСтатистики.Существует(), "Файл статистики должен быть записан");
+ юТест.ПроверитьБольше(ФайлСтатистики.Размер(), 0, "Файл статистики не должен быть пустым");
+ Исключение
+ УдалитьФайлы(ИмяФайлаСкрипта);
+ УдалитьФайлы(ИмяФайлаСтатистики);
+ ВызватьИсключение;
+ КонецПопытки;
+
+ УдалитьФайлы(ИмяФайлаСкрипта);
+ УдалитьФайлы(ИмяФайлаСтатистики);
+
+КонецПроцедуры
+
+Процедура ПроверитьУспешныйЗапуск(Знач ТекстСкрипта)
+
+ ИмяФайлаСкрипта = ПолучитьИмяВременногоФайла("os");
+ Запись = Новый ЗаписьТекста(ИмяФайлаСкрипта, КодировкаТекста.UTF8);
+ Запись.Записать(ТекстСкрипта);
+ Запись.Закрыть();
+
+ Попытка
+ Результат = ЗапуститьОскрипт("-codestat """ + ИмяФайлаСкрипта + """");
+ юТест.ПроверитьРавенство(0, Результат.КодВозврата, Результат.Вывод);
+ Исключение
+ УдалитьФайлы(ИмяФайлаСкрипта);
+ ВызватьИсключение;
+ КонецПопытки;
+
+ УдалитьФайлы(ИмяФайлаСкрипта);
+
+КонецПроцедуры
+
+Функция ЗапуститьОскрипт(Знач Аргументы)
+
+ СтрокаЗапуска = ПутьОСкрипт() + " " + Аргументы;
+ Процесс = СоздатьПроцесс(СтрокаЗапуска, , Истина);
+ Процесс.Запустить();
+
+ Вывод = "";
+ Пока Не Процесс.Завершен Или Процесс.ПотокВывода.ЕстьДанные Цикл
+ Стр = Процесс.ПотокВывода.Прочитать();
+ Если Стр <> Неопределено Тогда
+ Вывод = Вывод + Стр;
+ КонецЕсли;
+ КонецЦикла;
+
+ Возврат Новый Структура("КодВозврата, Вывод", Процесс.КодВозврата, Вывод);
+
+КонецФункции
+
+Функция ПутьОСкрипт()
+ Возврат "dotnet """ + ОбъединитьПути(КаталогПрограммы(), "oscript.dll") + """";
+КонецФункции
diff --git a/tests/process.os b/tests/process.os
index 44da651b5..44a385b04 100644
--- a/tests/process.os
+++ b/tests/process.os
@@ -162,7 +162,7 @@
|
|Options:
| -encoding= Set output encoding (e.g. utf-8).
- | -codestat= Write code execution statistics to file.
+ | -codestat[=] Enable code statistics; write to file if specified.
|
|CGI Mode:
| oscript.exe -cgi [script_arguments...]