diff --git a/src/OneScript.StandardLibrary/Text/ConsoleContext.cs b/src/OneScript.StandardLibrary/Text/ConsoleContext.cs
index 2ebe5f7c3..6648f666b 100644
--- a/src/OneScript.StandardLibrary/Text/ConsoleContext.cs
+++ b/src/OneScript.StandardLibrary/Text/ConsoleContext.cs
@@ -11,6 +11,7 @@ This Source Code Form is subject to the terms of the
using OneScript.Exceptions;
using OneScript.Execution;
using OneScript.StandardLibrary.Binary;
+using OneScript.Types;
using OneScript.Values;
using ScriptEngine.Machine;
using ScriptEngine.Machine.Contexts;
@@ -181,7 +182,7 @@ public IValue OutputEncoding
}
set
{
- Console.OutputEncoding = TextEncodingEnum.GetEncoding(value);
+ ConsoleWriterRouter.SetOutputEncoding(TextEncodingEnum.GetEncoding(value));
}
}
@@ -229,34 +230,61 @@ public GenericStream OpenStandardOutput()
}
///
- /// Глобально переопределяет стандартный вывод и направляет в другой поток
+ /// Переопределяет стандартный вывод и направляет в другой поток.
+ /// Действует в текущем потоке выполнения и в фоновых заданиях, запущенных из него после вызова.
///
- /// Поток назначения
+ /// Поток назначения. Неопределено - вернуть стандартный вывод
[ContextMethod("УстановитьПотокВывода", "SetOutput")]
public void SetOutput(IValue target)
{
- if (!(target.AsObject() is IStreamWrapper stream))
- throw RuntimeException.InvalidArgumentType(nameof(target));
-
- var writer = new StreamWriter(stream.GetUnderlyingStream(), Console.OutputEncoding)
- {
- AutoFlush = true,
- };
- Console.SetOut(writer);
+ ConsoleWriterRouter.SetOut(CreateWriter(target), target);
}
-
+
///
- /// Глобально переопределяет стандартный поток ошибок и направляет в другой поток
+ /// Возвращает поток, в который перенаправлен стандартный вывод текущего потока выполнения,
+ /// в том числе унаследованный фоновым заданием от запустившего его кода.
///
- /// Поток назначения
+ /// Поток, Неопределено - вывод не перенаправлен
+ [ContextMethod("ПолучитьПотокВывода", "GetOutput")]
+ public IValue GetOutput()
+ {
+ return ConsoleWriterRouter.GetOutSource() ?? BslUndefinedValue.Instance;
+ }
+
+ ///
+ /// Переопределяет стандартный поток ошибок и направляет в другой поток.
+ /// Действует в текущем потоке выполнения и в фоновых заданиях, запущенных из него после вызова.
+ ///
+ /// Поток назначения. Неопределено - вернуть стандартный поток ошибок
[ContextMethod("УстановитьПотокОшибок", "SetError")]
public void SetError(IValue target)
{
+ ConsoleWriterRouter.SetError(CreateWriter(target), target);
+ }
+
+ ///
+ /// Возвращает поток, в который перенаправлен поток ошибок текущего потока выполнения,
+ /// в том числе унаследованный фоновым заданием от запустившего его кода.
+ ///
+ /// Поток, Неопределено - поток ошибок не перенаправлен
+ [ContextMethod("ПолучитьПотокОшибок", "GetError")]
+ public IValue GetError()
+ {
+ return ConsoleWriterRouter.GetErrorSource() ?? BslUndefinedValue.Instance;
+ }
+
+ private static StreamWriter CreateWriter(IValue target)
+ {
+ if (target.SystemType == BasicTypes.Undefined)
+ return null;
+
if (!(target.AsObject() is IStreamWrapper stream))
throw RuntimeException.InvalidArgumentType(nameof(target));
-
- var writer = new StreamWriter(stream.GetUnderlyingStream());
- Console.SetError(writer);
+
+ return new StreamWriter(stream.GetUnderlyingStream(), Console.OutputEncoding)
+ {
+ AutoFlush = true,
+ };
}
public const string ConsoleCancelKeyEvent = "CancelKeyPressed";
diff --git a/src/OneScript.StandardLibrary/Text/ConsoleOutputTargets.cs b/src/OneScript.StandardLibrary/Text/ConsoleOutputTargets.cs
new file mode 100644
index 000000000..6bda09b77
--- /dev/null
+++ b/src/OneScript.StandardLibrary/Text/ConsoleOutputTargets.cs
@@ -0,0 +1,35 @@
+/*----------------------------------------------------------
+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/.
+----------------------------------------------------------*/
+
+namespace OneScript.StandardLibrary.Text
+{
+ ///
+ /// Куда текущий поток выполнения выводит консоль (Консоль.УстановитьПотокВывода/УстановитьПотокОшибок).
+ /// Фоновые задания наследуют это сами, а там, куда контекст выполнения не передается
+ /// (обработчики запросов веб-сервера), цели нужно перенести явно.
+ ///
+ public sealed class ConsoleOutputTargets
+ {
+ private readonly (object Out, object Error) _targets;
+
+ private ConsoleOutputTargets((object Out, object Error) targets)
+ {
+ _targets = targets;
+ }
+
+ ///
+ /// Запоминает цели вывода текущего потока выполнения
+ ///
+ public static ConsoleOutputTargets Capture() => new ConsoleOutputTargets(ConsoleWriterRouter.CaptureTargets());
+
+ ///
+ /// Устанавливает запомненные цели в текущем потоке выполнения.
+ /// Вызванный внутри async-метода, действует только до его завершения.
+ ///
+ public void Apply() => ConsoleWriterRouter.ApplyTargets(_targets);
+ }
+}
diff --git a/src/OneScript.StandardLibrary/Text/ConsoleWriterRouter.cs b/src/OneScript.StandardLibrary/Text/ConsoleWriterRouter.cs
new file mode 100644
index 000000000..5aa662407
--- /dev/null
+++ b/src/OneScript.StandardLibrary/Text/ConsoleWriterRouter.cs
@@ -0,0 +1,230 @@
+/*----------------------------------------------------------
+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.IO;
+using System.Text;
+using System.Threading;
+using ScriptEngine.Machine;
+
+namespace OneScript.StandardLibrary.Text
+{
+ ///
+ /// Стандартный вывод или поток ошибок консоли со своей целью у каждого потока выполнения.
+ /// Цель хранится в AsyncLocal: фоновые задания наследуют ее от запустившего их кода,
+ /// а переопределение внутри задания не затрагивает остальных.
+ ///
+ internal sealed class ConsoleWriterRouter : TextWriter
+ {
+ private static readonly object InstallLock = new object();
+ private static ConsoleWriterRouter _out;
+ private static ConsoleWriterRouter _error;
+
+ private readonly AsyncLocal _current = new AsyncLocal();
+ // Открывает стандартный поток, если вывод по умолчанию - собственный вывод консоли, а не заданный хостом
+ private readonly Func _openConsoleStream;
+ private volatile TextWriter _default;
+ private TextWriter _installed;
+
+ private ConsoleWriterRouter(TextWriter defaultWriter, Func openConsoleStream)
+ {
+ _default = defaultWriter;
+ _openConsoleStream = openConsoleStream;
+ }
+
+ ///
+ /// Направляет стандартный вывод текущего потока выполнения в writer; null возвращает вывод по умолчанию.
+ ///
+ public static void SetOut(TextWriter writer, IValue source)
+ {
+ lock (InstallLock)
+ {
+ // Console.Out мог заменить хост: тогда он становится выводом по умолчанию
+ if (!IsInstalled(_out, Console.Out))
+ {
+ if (writer == null)
+ return;
+
+ var isConsoleWriter = IsConsoleWriter(() => Console.Out);
+ _out = new ConsoleWriterRouter(Console.Out, isConsoleWriter ? Console.OpenStandardOutput : null);
+ Console.SetOut(_out);
+ _out._installed = Console.Out;
+ }
+
+ _out.SetCurrent(writer, source);
+ }
+ }
+
+ ///
+ /// Направляет поток ошибок текущего потока выполнения в writer; null возвращает поток ошибок по умолчанию.
+ ///
+ public static void SetError(TextWriter writer, IValue source)
+ {
+ lock (InstallLock)
+ {
+ if (!IsInstalled(_error, Console.Error))
+ {
+ if (writer == null)
+ return;
+
+ var isConsoleWriter = IsConsoleWriter(() => Console.Error);
+ _error = new ConsoleWriterRouter(Console.Error, isConsoleWriter ? Console.OpenStandardError : null);
+ Console.SetError(_error);
+ _error._installed = Console.Error;
+ }
+
+ _error.SetCurrent(writer, source);
+ }
+ }
+
+ ///
+ /// Меняет кодировку вывода консоли, в том числе вывода по умолчанию у установленных роутеров.
+ ///
+ public static void SetOutputEncoding(Encoding encoding)
+ {
+ lock (InstallLock)
+ {
+ Console.OutputEncoding = encoding;
+
+ // Console пересоздает свой вывод с новой кодировкой, только пока его не подменили через SetOut
+ _out?.RecreateConsoleWriter();
+ _error?.RecreateConsoleWriter();
+ }
+ }
+
+ // Цели текущего потока выполнения - для мест, куда контекст выполнения не передается сам
+ internal static (object Out, object Error) CaptureTargets()
+ {
+ lock (InstallLock)
+ {
+ return (_out?._current.Value, _error?._current.Value);
+ }
+ }
+
+ internal static void ApplyTargets((object Out, object Error) targets)
+ {
+ lock (InstallLock)
+ {
+ if (_out != null)
+ _out._current.Value = (Target)targets.Out;
+ if (_error != null)
+ _error._current.Value = (Target)targets.Error;
+ }
+ }
+
+ ///
+ /// Поток, в который перенаправлен стандартный вывод текущего потока выполнения, или null.
+ ///
+ public static IValue GetOutSource() => GetSource(_out, Console.Out);
+
+ ///
+ /// Поток, в который перенаправлен поток ошибок текущего потока выполнения, или null.
+ ///
+ public static IValue GetErrorSource() => GetSource(_error, Console.Error);
+
+ private static bool IsInstalled(ConsoleWriterRouter router, TextWriter console)
+ => router != null && ReferenceEquals(console, router._installed);
+
+ private static IValue GetSource(ConsoleWriterRouter router, TextWriter console)
+ => IsInstalled(router, console) ? router._current.Value?.Source : null;
+
+ // Публично не узнать, подменял ли хост вывод консоли. Но при смене кодировки Console пересоздает
+ // только свой вывод: присваиваем ту же кодировку и смотрим, сменился ли объект
+ private static bool IsConsoleWriter(Func consoleWriter)
+ {
+ var writer = consoleWriter();
+ try
+ {
+ var encoding = Console.OutputEncoding;
+ Console.OutputEncoding = encoding;
+ }
+ catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is PlatformNotSupportedException)
+ {
+ // Без консоли кодировку не сменить, и пересоздавать вывод не придется
+ return false;
+ }
+
+ return !ReferenceEquals(writer, consoleWriter());
+ }
+
+ private void RecreateConsoleWriter()
+ {
+ if (_openConsoleStream == null)
+ return;
+
+ var previous = _default;
+ _default = CreateConsoleWriter(_openConsoleStream());
+ previous.Flush();
+ }
+
+ // Как свой вывод создает Console: без BOM и со сбросом после каждой записи
+ private static TextWriter CreateConsoleWriter(Stream stream)
+ {
+ var writer = new StreamWriter(stream, WithoutPreamble(Console.OutputEncoding), 256, leaveOpen: true)
+ {
+ AutoFlush = true
+ };
+ return TextWriter.Synchronized(writer);
+ }
+
+ private static Encoding WithoutPreamble(Encoding encoding)
+ {
+ if (encoding.GetPreamble().Length == 0)
+ return encoding;
+
+ return encoding.CodePage switch
+ {
+ 65001 => new UTF8Encoding(false),
+ 1200 => new UnicodeEncoding(false, false),
+ 1201 => new UnicodeEncoding(true, false),
+ 12000 => new UTF32Encoding(false, false),
+ 12001 => new UTF32Encoding(true, false),
+ _ => encoding
+ };
+ }
+
+ private void SetCurrent(TextWriter writer, IValue source)
+ {
+ _current.Value = writer == null ? null : new Target(writer, source);
+ }
+
+ private TextWriter Writer => _current.Value?.Writer ?? _default;
+
+ public override Encoding Encoding => Writer.Encoding;
+
+ public override IFormatProvider FormatProvider => Writer.FormatProvider;
+
+ public override void Write(char value) => Writer.Write(value);
+
+ public override void Write(char[] buffer, int index, int count) => Writer.Write(buffer, index, count);
+
+ public override void Write(ReadOnlySpan buffer) => Writer.Write(buffer);
+
+ public override void Write(string value) => Writer.Write(value);
+
+ public override void WriteLine() => Writer.WriteLine();
+
+ public override void WriteLine(ReadOnlySpan buffer) => Writer.WriteLine(buffer);
+
+ public override void WriteLine(string value) => Writer.WriteLine(value);
+
+ public override void Flush() => Writer.Flush();
+
+ private sealed class Target
+ {
+ public Target(TextWriter writer, IValue source)
+ {
+ Writer = writer;
+ Source = source;
+ }
+
+ public TextWriter Writer { get; }
+
+ public IValue Source { get; }
+ }
+ }
+}
diff --git a/src/OneScript.Web.Server/WebServer.cs b/src/OneScript.Web.Server/WebServer.cs
index d06a876b9..68e5a2a50 100644
--- a/src/OneScript.Web.Server/WebServer.cs
+++ b/src/OneScript.Web.Server/WebServer.cs
@@ -9,6 +9,7 @@ This Source Code Form is subject to the terms of the
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using OneScript.Contexts;
+using OneScript.StandardLibrary.Text;
using OneScript.Types;
using ScriptEngine.Machine;
using ScriptEngine.Machine.Contexts;
@@ -123,6 +124,16 @@ private void ConfigureApp()
_app = builder.Build();
+ // Обработчики запросов, как и фоновые задания, выводят консоль туда же, куда тот,
+ // кто запустил сервер. Переносятся только цели вывода, остальной контекст запроса
+ // (трассировка и т.п.) остается свой; async - чтобы цели не вышли за пределы запроса
+ var consoleTargets = ConsoleOutputTargets.Capture();
+ _app.Use(async (context, next) =>
+ {
+ consoleTargets.Apply();
+ await next();
+ });
+
if (_useStaticFiles)
_app.UseStaticFiles();
diff --git a/src/Tests/OneScript.Core.Tests/WebServerOutputTests.cs b/src/Tests/OneScript.Core.Tests/WebServerOutputTests.cs
new file mode 100644
index 000000000..c0c1de89c
--- /dev/null
+++ b/src/Tests/OneScript.Core.Tests/WebServerOutputTests.cs
@@ -0,0 +1,125 @@
+/*----------------------------------------------------------
+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.IO;
+using System.Net;
+using System.Net.Sockets;
+using FluentAssertions;
+using OneScript.Contexts;
+using OneScript.StandardLibrary;
+using OneScript.StandardLibrary.Binary;
+using OneScript.Web.Server;
+using ScriptEngine.HostedScript;
+using ScriptEngine.HostedScript.Extensions;
+using ScriptEngine.Hosting;
+using ScriptEngine.Machine;
+using ScriptEngine.Machine.Contexts;
+using Xunit;
+
+namespace OneScript.Core.Tests
+{
+ [GlobalContext(ManualRegistration = true)]
+ public class RequestTraceProbe : GlobalContextBase
+ {
+ [ContextMethod("ТрассировкаЗапроса")]
+ public string CurrentActivity() => System.Diagnostics.Activity.Current?.Id ?? "";
+ }
+
+ public class WebServerOutputTests
+ {
+ private const string Script =
+ "Перем мСервер;\n" +
+ "\n" +
+ "Функция ОбработчикЗапроса(Контекст, СледующийОбработчик) Экспорт\n" +
+ " Консоль.ВывестиСтроку(\"from request\");\n" +
+ " Контекст.Ответ.КодСостояния = 200;\n" +
+ "КонецФункции\n" +
+ "\n" +
+ "Процедура ЗапуститьСервер() Экспорт\n" +
+ " мСервер.Запустить();\n" +
+ "КонецПроцедуры\n" +
+ "\n" +
+ "мСервер = Новый ВебСервер(Порт);\n" +
+ "мСервер.ДобавитьОбработчикЗапросов(ЭтотОбъект, \"ОбработчикЗапроса\");\n" +
+ "Консоль.УстановитьПотокВывода(Вывод);\n" +
+ "Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, \"ЗапуститьСервер\");\n" +
+ "Соединение = Новый HTTPСоединение(\"http://127.0.0.1:\" + Формат(Порт, \"ЧГ=\"));\n" +
+ "Для Номер = 1 По 100 Цикл\n" +
+ " Попытка\n" +
+ " Соединение.Получить(Новый HTTPЗапрос(\"/\"));\n" +
+ " Прервать;\n" +
+ " Исключение\n" +
+ " Приостановить(100);\n" +
+ " КонецПопытки;\n" +
+ "КонецЦикла;\n" +
+ "мСервер.Остановить();\n" +
+ "Задание.ОжидатьЗавершения(10000);\n";
+
+ [Fact]
+ public void RequestHandlersWriteToOutputOfServerStarter()
+ {
+ var engine = CreateEngine();
+ var output = MemoryStreamContext.Constructor();
+ var context = new ExternalContextData
+ {
+ { "Порт", ValueFactory.Create(FreePort()) },
+ { "Вывод", output }
+ };
+
+ engine.Engine.AttachedScriptsFactory.LoadFromString(
+ engine.GetCompilerService(), Script, engine.Engine.NewProcess(), context);
+
+ // Текст латиницей: кодировка консоли на агентах CI может не содержать кириллицу
+ var bytes = ((MemoryStream)output.GetUnderlyingStream()).ToArray();
+ using var reader = new StreamReader(new MemoryStream(bytes), System.Console.OutputEncoding, true);
+ reader.ReadToEnd().Should().Contain("from request");
+ }
+
+ [Fact]
+ public void RequestHandlersKeepRequestExecutionContext()
+ {
+ var engine = CreateEngine();
+ engine.Engine.Environment.InjectObject(new RequestTraceProbe());
+ var context = new ExternalContextData
+ {
+ { "Порт", ValueFactory.Create(FreePort()) },
+ { "Вывод", MemoryStreamContext.Constructor() }
+ };
+
+ var script = Script.Replace(
+ " Консоль.ВывестиСтроку(\"from request\");\n",
+ " Трассировка = ТрассировкаЗапроса();\n");
+ var instance = engine.Engine.AttachedScriptsFactory.LoadFromString(
+ engine.GetCompilerService(), "Перем Трассировка Экспорт;\n" + script, engine.Engine.NewProcess(), context);
+
+ // Активность запроса ASP.NET Core хранится в AsyncLocal контекста запроса:
+ // перенос вывода консоли не должен подменять этот контекст контекстом старта сервера
+ instance.GetPropValue(instance.GetPropertyNumber("Трассировка")).ToString().Should().NotBeEmpty();
+ }
+
+ private static HostedScriptEngine CreateEngine()
+ {
+ var builder = DefaultEngineBuilder.Create()
+ .SetDefaultOptions()
+ .UseImports()
+ .UseDefaultHosting()
+ .SetupEnvironment(e => e.AddStandardLibrary().AddWebServer());
+ var engine = new HostedScriptEngine(builder.Build());
+ engine.Initialize();
+ return engine;
+ }
+
+ private static int FreePort()
+ {
+ var listener = new TcpListener(IPAddress.Loopback, 0);
+ listener.Start();
+ var port = ((IPEndPoint)listener.LocalEndpoint).Port;
+ listener.Stop();
+ return port;
+ }
+ }
+}
diff --git a/src/Tests/OneScript.StandardLibrary.Tests/ConsoleRedirectionTests.cs b/src/Tests/OneScript.StandardLibrary.Tests/ConsoleRedirectionTests.cs
new file mode 100644
index 000000000..6fd827fd1
--- /dev/null
+++ b/src/Tests/OneScript.StandardLibrary.Tests/ConsoleRedirectionTests.cs
@@ -0,0 +1,96 @@
+/*----------------------------------------------------------
+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.IO;
+using System.Linq;
+using System.Threading;
+using FluentAssertions;
+using OneScript.StandardLibrary.Binary;
+using OneScript.StandardLibrary.Text;
+using Xunit;
+
+namespace OneScript.StandardLibrary.Tests
+{
+ public class ConsoleRedirectionTests
+ {
+ [Fact]
+ public void ErrorStreamIsWrittenWithoutFlush()
+ {
+ var console = new ConsoleContext(null);
+ var target = MemoryStreamContext.Constructor();
+
+ RunInThreads(1, _ =>
+ {
+ console.SetError(target);
+ Console.Error.Write("ошибка");
+ });
+
+ ReadText(target).Should().Be("ошибка");
+ }
+
+ [Fact]
+ public void EachThreadWritesToItsOwnErrorStream()
+ {
+ const int threadsCount = 8;
+ var console = new ConsoleContext(null);
+ var targets = Enumerable.Range(0, threadsCount).Select(_ => MemoryStreamContext.Constructor()).ToArray();
+ using var barrier = new Barrier(threadsCount);
+
+ RunInThreads(threadsCount, number =>
+ {
+ console.SetError(targets[number]);
+ // Все потоки перенаправлены до того, как кто-то начнет писать
+ barrier.SignalAndWait();
+ for (var i = 0; i < 100; i++)
+ {
+ Console.Error.WriteLine($"поток {number}");
+ }
+ });
+
+ for (var number = 0; number < threadsCount; number++)
+ {
+ ReadText(targets[number]).Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)
+ .Should().HaveCount(100).And.OnlyContain(x => x == $"поток {number}");
+ }
+ }
+
+ private static void RunInThreads(int threadsCount, Action action)
+ {
+ Exception error = null;
+ var threads = Enumerable.Range(0, threadsCount).Select(number => new Thread(() =>
+ {
+ try
+ {
+ action(number);
+ }
+ catch (Exception e)
+ {
+ error = e;
+ }
+ })).ToArray();
+
+ foreach (var thread in threads)
+ {
+ thread.Start();
+ }
+ foreach (var thread in threads)
+ {
+ thread.Join();
+ }
+
+ error.Should().BeNull();
+ }
+
+ private static string ReadText(MemoryStreamContext stream)
+ {
+ var bytes = ((MemoryStream)stream.GetUnderlyingStream()).ToArray();
+ using var reader = new StreamReader(new MemoryStream(bytes), Console.OutputEncoding, true);
+ return reader.ReadToEnd();
+ }
+ }
+}
diff --git a/src/oscript/Output.cs b/src/oscript/Output.cs
index 73f026b74..cfcbac82e 100644
--- a/src/oscript/Output.cs
+++ b/src/oscript/Output.cs
@@ -47,8 +47,8 @@ private static void Init()
public static void WriteLine(string text)
{
- Write(text);
- WriteLine();
+ // Одной записью: иначе строки из фоновых заданий склеиваются
+ Write(text + Environment.NewLine);
}
public static void WriteLine()
diff --git a/tests/console.os b/tests/console.os
index 2f6429031..45a0ab981 100644
--- a/tests/console.os
+++ b/tests/console.os
@@ -12,6 +12,13 @@
ВсеТесты.Добавить("ТестДолжен_ПроверитьПередачуДанныхВСкриптЧерезСтандартныйПотокВвода");
#КонецЕсли
ВсеТесты.Добавить("ТестДолжен_ПроверитьПеренаправлениеВывода");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьПеренаправлениеВыводаВФоновыхЗаданиях");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоФоновоеЗаданиеНаследуетПотокВывода");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоСтрокиЗаданийНеСклеиваются");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьПолучениеПотокаВывода");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьПолучениеПотокаОшибок");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьЧтоЗаданиеПолучаетУнаследованныйПотокВывода");
+ ВсеТесты.Добавить("ТестДолжен_ПроверитьСменуКодировкиВыводаПослеПеренаправленияВЗадании");
ВсеТесты.Добавить("ТестДолжен_ПроверитЦветКонсоли");
Возврат ВсеТесты;
@@ -145,6 +152,211 @@
КонецПроцедуры
+Процедура ТестДолжен_ПроверитьПеренаправлениеВыводаВФоновыхЗаданиях() Экспорт
+
+ ВыводТеста = Новый ПотокВПамяти;
+ ВыводЗаданий = Новый Массив;
+ Задания = Новый Массив;
+
+ Консоль.УстановитьПотокВывода(ВыводТеста);
+ Попытка
+ Для Номер = 1 По 8 Цикл
+ ВыводЗадания = Новый ПотокВПамяти;
+ ВыводЗаданий.Добавить(ВыводЗадания);
+ Параметры = Новый Массив;
+ Параметры.Добавить(ВыводЗадания);
+ Параметры.Добавить("задание " + Номер);
+ Задания.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ВывестиСтрокиВСвойПоток", Параметры));
+ КонецЦикла;
+ // Пока задания пишут в свои потоки
+ ВывестиСтроки("тест");
+ ФоновыеЗадания.ОжидатьВсе(Задания);
+ Исключение
+ Консоль.УстановитьПотокВывода(Консоль.ОткрытьСтандартныйПотокВывода());
+ ВызватьИсключение;
+ КонецПопытки;
+ Консоль.УстановитьПотокВывода(Консоль.ОткрытьСтандартныйПотокВывода());
+
+ ПроверитьЧтоЗаданияБезОшибок(Задания);
+ ПроверитьВыведенныеСтроки(ВыводТеста, "тест", КоличествоСтрокВывода());
+ Для Номер = 1 По 8 Цикл
+ ПроверитьВыведенныеСтроки(ВыводЗаданий[Номер - 1], "задание " + Номер, КоличествоСтрокВывода());
+ КонецЦикла;
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьЧтоФоновоеЗаданиеНаследуетПотокВывода() Экспорт
+
+ Вывод = Новый ПотокВПамяти;
+
+ Консоль.УстановитьПотокВывода(Вывод);
+ Попытка
+ Параметры = Новый Массив;
+ Параметры.Добавить("из задания");
+ Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ВывестиСтроки", Параметры);
+ Задание.ОжидатьЗавершения();
+ Исключение
+ Консоль.УстановитьПотокВывода(Консоль.ОткрытьСтандартныйПотокВывода());
+ ВызватьИсключение;
+ КонецПопытки;
+ Консоль.УстановитьПотокВывода(Консоль.ОткрытьСтандартныйПотокВывода());
+
+ ПроверитьЧтоЗаданияБезОшибок(МассивИз(Задание));
+ ПроверитьВыведенныеСтроки(Вывод, "из задания", КоличествоСтрокВывода());
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьЧтоСтрокиЗаданийНеСклеиваются() Экспорт
+
+ Вывод = Новый ПотокВПамяти;
+ Задания = Новый Массив;
+
+ Консоль.УстановитьПотокВывода(Вывод);
+ Попытка
+ Для Номер = 1 По 8 Цикл
+ Параметры = Новый Массив;
+ Параметры.Добавить("задание");
+ Задания.Добавить(ФоновыеЗадания.Выполнить(ЭтотОбъект, "ВывестиСтроки", Параметры));
+ КонецЦикла;
+ ФоновыеЗадания.ОжидатьВсе(Задания);
+ Исключение
+ Консоль.УстановитьПотокВывода(Консоль.ОткрытьСтандартныйПотокВывода());
+ ВызватьИсключение;
+ КонецПопытки;
+ Консоль.УстановитьПотокВывода(Консоль.ОткрытьСтандартныйПотокВывода());
+
+ ПроверитьЧтоЗаданияБезОшибок(Задания);
+ ПроверитьВыведенныеСтроки(Вывод, "задание", 8 * КоличествоСтрокВывода());
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьПолучениеПотокаВывода() Экспорт
+
+ Прежний = Консоль.ПолучитьПотокВывода();
+ Лог = Новый ПотокВПамяти;
+
+ Консоль.УстановитьПотокВывода(Лог);
+ Текущий = Консоль.ПолучитьПотокВывода();
+ Консоль.УстановитьПотокВывода(Неопределено);
+ ПослеСброса = Консоль.ПолучитьПотокВывода();
+ Консоль.УстановитьПотокВывода(Прежний);
+
+ юТест.ПроверитьРавенство(Лог, Текущий);
+ юТест.ПроверитьРавенство(Неопределено, ПослеСброса);
+ юТест.ПроверитьРавенство(Прежний, Консоль.ПолучитьПотокВывода());
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьПолучениеПотокаОшибок() Экспорт
+
+ Прежний = Консоль.ПолучитьПотокОшибок();
+ Лог = Новый ПотокВПамяти;
+
+ Консоль.УстановитьПотокОшибок(Лог);
+ Текущий = Консоль.ПолучитьПотокОшибок();
+ Консоль.УстановитьПотокОшибок(Неопределено);
+ ПослеСброса = Консоль.ПолучитьПотокОшибок();
+ Консоль.УстановитьПотокОшибок(Прежний);
+
+ юТест.ПроверитьРавенство(Лог, Текущий);
+ юТест.ПроверитьРавенство(Неопределено, ПослеСброса);
+ юТест.ПроверитьРавенство(Прежний, Консоль.ПолучитьПотокОшибок());
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьЧтоЗаданиеПолучаетУнаследованныйПотокВывода() Экспорт
+
+ Прежний = Консоль.ПолучитьПотокВывода();
+ Лог = Новый ПотокВПамяти;
+
+ Консоль.УстановитьПотокВывода(Лог);
+ Попытка
+ Задание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПотокВыводаЗадания");
+ Задание.ОжидатьЗавершения();
+ Исключение
+ Консоль.УстановитьПотокВывода(Прежний);
+ ВызватьИсключение;
+ КонецПопытки;
+ Консоль.УстановитьПотокВывода(Прежний);
+
+ ПроверитьЧтоЗаданияБезОшибок(МассивИз(Задание));
+ юТест.ПроверитьРавенство(Лог, Задание.Результат);
+
+КонецПроцедуры
+
+Процедура ТестДолжен_ПроверитьСменуКодировкиВыводаПослеПеренаправленияВЗадании() Экспорт
+
+ // В отдельном процессе: кодировка консоли общая на процесс
+ КодСкрипта = "Процедура Перенаправить() Экспорт
+ | Консоль.УстановитьПотокВывода(Новый ПотокВПамяти);
+ |КонецПроцедуры
+ |ФоновыеЗадания.Выполнить(ЭтотОбъект, ""Перенаправить"").ОжидатьЗавершения();
+ |Консоль.КодировкаВыходногоПотока = КодировкаТекста.UTF16;
+ |Консоль.Вывести(""Привет"");";
+
+ ТекстСкрипта = Новый ТекстовыйДокумент();
+ ТекстСкрипта.УстановитьТекст(КодСкрипта);
+ ВремФайл = ПолучитьИмяВременногоФайла("os");
+ ТекстСкрипта.Записать(ВремФайл);
+
+ ПутьКОскрипт = ОбъединитьПути(КаталогПрограммы(), "oscript.dll");
+ Процесс = СоздатьПроцесс(СтрШаблон("dotnet ""%1"" ""%2""", ПутьКОскрипт, ВремФайл), , Истина, , КодировкаТекста.UTF16);
+ Процесс.Запустить();
+ Процесс.ОжидатьЗавершения();
+ Вывод = Процесс.ПотокВывода.Прочитать();
+ УдалитьФайлы(ВремФайл);
+
+ юТест.ПроверитьРавенство("Привет", Вывод, "Вывод основного потока в новой кодировке");
+
+КонецПроцедуры
+
+Функция ПотокВыводаЗадания() Экспорт
+ Возврат Консоль.ПолучитьПотокВывода();
+КонецФункции
+
+Процедура ВывестиСтрокиВСвойПоток(Поток, Текст) Экспорт
+ Консоль.УстановитьПотокВывода(Поток);
+ ВывестиСтроки(Текст);
+КонецПроцедуры
+
+Процедура ВывестиСтроки(Текст) Экспорт
+ Для Счетчик = 1 По КоличествоСтрокВывода() Цикл
+ Сообщить(Текст);
+ КонецЦикла;
+КонецПроцедуры
+
+Функция КоличествоСтрокВывода()
+ Возврат 200;
+КонецФункции
+
+Функция МассивИз(Элемент)
+ Результат = Новый Массив;
+ Результат.Добавить(Элемент);
+ Возврат Результат;
+КонецФункции
+
+Процедура ПроверитьЧтоЗаданияБезОшибок(Задания)
+ Для Каждого Задание Из Задания Цикл
+ Если Задание.ИнформацияОбОшибке <> Неопределено Тогда
+ ВызватьИсключение Задание.ИнформацияОбОшибке.ПодробноеОписаниеОшибки();
+ КонецЕсли;
+ КонецЦикла;
+КонецПроцедуры
+
+Процедура ПроверитьВыведенныеСтроки(Поток, ОжидаемаяСтрока, ОжидаемоеКоличество)
+
+ Поток.Перейти(0, ПозицияВПотоке.Начало);
+ Чтение = Новый ЧтениеТекста(Поток, Консоль.КодировкаВыходногоПотока);
+ Строки = СтрРазделить(Чтение.Прочитать(), Символы.ПС, Ложь);
+ Чтение.Закрыть();
+
+ юТест.ПроверитьРавенство(ОжидаемоеКоличество, Строки.Количество(), "Количество строк " + ОжидаемаяСтрока);
+ Для Каждого Строка Из Строки Цикл
+ юТест.ПроверитьРавенство(ОжидаемаяСтрока, СокрЛП(Строка));
+ КонецЦикла;
+
+КонецПроцедуры
+
Процедура ТестДолжен_ПроверитЦветКонсоли() Экспорт
// Получение цвета не должно упасть