# HG changeset patch # User user@factory.site.local # Date 2013-08-23 00:38:46 # Node ID 279591fb4df3bcfb6ef677edcb43c43a114d5788 # Parent 0000000000000000000000000000000000000000 initial commit promises async model diff --git a/.hgignore b/.hgignore new file mode 100644 --- /dev/null +++ b/.hgignore @@ -0,0 +1,8 @@ +syntax: glob +Implab.Test/bin/ +*.user +Implab.Test/obj/ +*.userprefs +Implab/bin/ +Implab/obj/ +TestResults/ diff --git a/Implab.Test/AsyncTests.cs b/Implab.Test/AsyncTests.cs new file mode 100644 --- /dev/null +++ b/Implab.Test/AsyncTests.cs @@ -0,0 +1,101 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Implab; +using System.Reflection; +using System.Threading; + +namespace Implab.Tests +{ + [TestClass] + public class AsyncTests + { + [TestMethod] + public void ResolveTest () + { + int res = -1; + var p = new Promise (); + p.Then (x => res = x); + p.Resolve (100); + + Assert.AreEqual (res, 100); + } + + [TestMethod] + public void RejectTest () + { + int res = -1; + Exception err = null; + + var p = new Promise (); + p.Then (x => res = x, e => err = e); + p.Reject (new ApplicationException ("error")); + + Assert.AreEqual (res, -1); + Assert.AreEqual (err.Message, "error"); + + } + + [TestMethod] + public void JoinSuccessTest () + { + var p = new Promise (); + p.Resolve (100); + Assert.AreEqual (p.Join (), 100); + } + + [TestMethod] + public void JoinFailTest () + { + var p = new Promise (); + p.Reject (new ApplicationException ("failed")); + + try { + p.Join (); + throw new ApplicationException ("WRONG!"); + } catch (TargetInvocationException err) { + Assert.AreEqual (err.InnerException.Message, "failed"); + } catch { + Assert.Fail ("Got wrong excaption"); + } + } + + [TestMethod] + public void MapTest () + { + var p = new Promise (); + + var p2 = p.Map (x => x.ToString ()); + p.Resolve (100); + + Assert.AreEqual (p2.Join (), "100"); + } + + [TestMethod] + public void ChainTest () + { + var p1 = new Promise (); + + var p3 = p1.Chain (x => { + var p2 = new Promise (); + p2.Resolve (x.ToString ()); + return p2; + }); + + p1.Resolve (100); + + Assert.AreEqual (p3.Join (), "100"); + } + + [TestMethod] + public void PoolTest () + { + var pid = Thread.CurrentThread.ManagedThreadId; + var p = AsyncPool.Invoke (() => { + return Thread.CurrentThread.ManagedThreadId; + }); + + Assert.AreNotEqual (pid, p.Join ()); + } + } +} + diff --git a/Implab.Test/Implab.Test.csproj b/Implab.Test/Implab.Test.csproj new file mode 100644 --- /dev/null +++ b/Implab.Test/Implab.Test.csproj @@ -0,0 +1,65 @@ + + + + Debug + AnyCPU + + + 2.0 + {63F92C0C-61BF-48C0-A377-8D67C3C661D0} + Library + Properties + Implab.Test + Implab.Test + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + 3.5 + + + + + False + + + + + + + + + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9} + Implab + + + + + \ No newline at end of file diff --git a/Implab.Test/Properties/AssemblyInfo.cs b/Implab.Test/Properties/AssemblyInfo.cs new file mode 100644 --- /dev/null +++ b/Implab.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Implab.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Implab.Test")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bfcae720-21eb-4411-b70a-6eeab99071de")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Implab.sln b/Implab.sln new file mode 100644 --- /dev/null +++ b/Implab.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab", "Implab\Implab.csproj", "{99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CE8D8D18-437A-445C-B662-4C2CE79A76F6}" + ProjectSection(SolutionItems) = preProject + Implab.vsmdi = Implab.vsmdi + Local.testsettings = Local.testsettings + TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Implab.Test", "Implab.Test\Implab.Test.csproj", "{63F92C0C-61BF-48C0-A377-8D67C3C661D0}" +EndProject +Global + GlobalSection(TestCaseManagementSettings) = postSolution + CategoryFile = Implab.vsmdi + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}.Release|Any CPU.Build.0 = Release|Any CPU + {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63F92C0C-61BF-48C0-A377-8D67C3C661D0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Implab\Implab.csproj + EndGlobalSection +EndGlobal diff --git a/Implab.suo b/Implab.suo new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..15dcf3269422ccfd545ebcb6e6d25425c37cd74a GIT binary patch literal 19968 zc%1E9U2q#$6~1LES*y`X zN!%ob7am$#V1{9UPKUyDpbWf}4$~QCVA?4IPn|YA@X!}ZpZdZB52e#Skno+ed!^M{ zKf7zEv5T+fNV|9Mz2}^J&pm&4b^V>K|NO=8cDyfaR9iL)M0-LnSQM|T-@6Tqm6^I{a(3%E*&%P#TvHt8Iv{kE9D zQz9#LaY2j;6~9$+8s7?NlJ$C*arTMc9@+;a`%dwb4qaczuZl8efpF?34GQKWkMAVl zq6mqgIM{Zq10MJ%oh=*6eMxIVdSIPs-me7K{ZINp?thv?x&Nu%c1z#G!q5*=2atYw z5a4A0?+1Jd@UUe(nt?um9}ob18Sqs=2yg;000;w4TKWu`*CY5nWT}4?*GB+H0Wm-v zkN_kBDL@)<4Dc9W)a-X0zhi)L%QzFbe%!pD!gE&LgRc}NI5jD z`YgV)kV7AHRb+|sVZ|3yv(mf7b#BUH z_4fvmT0>Mk(~}$t;&WwqI6gd>3?}`pI&S>6*h_9_kET8P794P!gRKGGF2j87ae*g^lyW4&_dU+hT# zeh>bqHD6%IKaTG?wqo8~`jghB z&x8Dvzx0%8cuzw5&O_=+2iwD&Uc=%V=6?n&)vR|;viD@wIutFo)=h?VQ`_|%_ciOL z#nE<%qr0UXF(6KX;v#DZlFh4VQG%xOq16QBH2}$p*FESsTFx}h!;vMYr=h>x(qdSG z1T@ePMj;5{nV)@Erkt5yz5XV2uC=@k=8G8AAlv z%Ymo@~*^o|8vj1{bVAOd@VQD_rdcQKa%yE`aa3Oo_*u7 zmo7aw`{%!AuM2bhcH_v>>iIfTf;wd{n(qwDF~`w|!Ye|43E9927vlur0m|7q4XGwC zN7jVuqmq@J>Nx5?P)`~33#iNTbDd+OAU+|EfkMptLcFxDF>&^t4g9=OD{(uEEaKzo zshupDCm^>|kXIv?Q|hbS2Wn(?nf!~x*yUlSj4&jaE}gv%m3y`GG+UQ_rvFDiofrR0 z`T^tz(;R8+e^*S6UhR_oF!1jYjdMpQ_z#Fi{)5(@ux2?How4~P-7{c&Jovv>{vX4v z&oJ&1crM~Q%^vBDxn>UVFDm{b%aDhZ%rf6K_Pxi<`RL?#%i<;e3j&@subbz43U@W$ z2l70q!uzVd4^Ht1St+mlpJd;^$WaHutVQyYsHt=rYo7=H`vC=Wd)u{#Q@NMrLGUkA z=uYtu3gc{riv9d+5FfFLwd%Jd@!SZ9(C6k`8oKzySv zB@1U~#N2$wA~An367C-w2o8oL(eSnBJ>~)((Nb0_ zRaMb5SzTA=a>aSIRM1?|)+6ddpdR&_w7eCQ)+>_Bi5$D6+%J0IaqO*cppRdt(_=dA zPA=!=Zq0_ZP~vU3acTEFGj&fh!}(<6-G({TBuqaq;3#^@v6hfg(3r;+PZ%Bf8Kr>(sY(bdmEAciAJ;W{T$Pew)N&WEo!FNV;+|5ft{5>L+eB2^%&BfB5RL zt=ez@a`tQ6qCeC4n3qm-X4Up{7$wj3h9m}BXhz~FzhXFQXTMtW)lSjN1x~il@UO{#YZW$;IPEm8R`>g@mz9B z?__pvY1jXYTiM0tA7%X6#z1u~r@W+mJ3B|Oy!i8L@$diR%^$rH`PRE1ir-U$c7a7+ zs&cz`#x!cCO#4c2dt62|hAHE_Q9!YclFUfq<*?Wnp=w5tg z{^cGQ_?_neDF4#mWvqQ3@;_IEsArL;%p66!o_%d(JDc~7%*$7r-pLv9kbfHn{Mx*B zqZw1sjCT3E-T9vrOMB$=ub2Ox+y2pJpZ3xp|JMJfbDN2vj(gu}{;3`Rw>y6{3AXd* zF1*NR5hQb@;b+Bhe0Tdd2+2SBxH7DAB7bi7-#~Z#EOlF5;HT)}I}Y)O9pHDn|0uF_ zn%{R)7Gs#~!o6DlzY2M~_H+3s?=Z48^nV;Tv8$K!gHG?4$kHkQV(8sNP~cHMw~<%< zrgW2iE1NO3@m5fCX_YOJJHvQWQ)c;A^DnFUmz5F&ujXG?CSR-hm-hJ=vPc0}yV>gS zx2|utQa`wFOF#WTdEk~}3DouELvLL?Ncg;2!c9$<@DQ+_Lazk>gVnWFv>obowu(C( zS;YqTi?ff9cwUY6DV~*O!y=w%z>BXSHO_ZkYaH+Y5=*co&hb1&nIDpD!dx>oRo=1g z3N+!gVho7K%~}fm=j(YdnrnMPVBvbUvo)VRui&6dpGjt|yLrfjgZ|gQl>dNO^<+ + + + + + \ No newline at end of file diff --git a/Implab/AsyncPool.cs b/Implab/AsyncPool.cs new file mode 100644 --- /dev/null +++ b/Implab/AsyncPool.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; + +namespace Implab { + /// + /// Класс для распаралеливания задач. + /// + /// + /// Используя данный класс и лямда выражения можно распараллелить + /// вычисления, для этого используется концепция обещаний. + /// + public static class AsyncPool { + + public static Promise Invoke(Func func) { + var p = new Promise(); + + ThreadPool.QueueUserWorkItem(param => { + try { + p.Resolve(func()); + } catch(Exception e) { + p.Reject(e); + } + }); + + return p; + } + } +} diff --git a/Implab/Implab.csproj b/Implab/Implab.csproj new file mode 100644 --- /dev/null +++ b/Implab/Implab.csproj @@ -0,0 +1,43 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9} + Library + Implab + Implab + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + full + true + bin\Release + prompt + 4 + false + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Implab/Promise.cs b/Implab/Promise.cs new file mode 100644 --- /dev/null +++ b/Implab/Promise.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Diagnostics; +using System.Threading; + +namespace Implab { + + public delegate void ErrorHandler(Exception e); + + public delegate void ResultHandler(T result); + public delegate TNew ResultMapper(TSrc result); + public delegate Promise ChainedOperation(TSrc result); + + /// + /// Класс для асинхронного получения результатов. Так называемое "обещание". + /// + /// Тип получаемого результата + /// + /// Сервис при обращении к его методу дает обещаиние о выполнении операции, + /// клиент получив такое обещание может установить ряд обратных вызово для получения + /// событий выполнения обещания, тоесть завершения операции и предоставлении результатов. + /// + /// Обещение может быть как выполнено, так и выполнено с ошибкой. Для подписки на + /// данные события клиент должен использовать методы Then. + /// + /// + /// Сервис, в свою очередь, по окончанию выполнения операции (возможно с ошибкой), + /// использует методы Resolve либо Reject для оповещения клиетна о + /// выполнении обещания. + /// + /// + /// Если сервер успел выполнить обещание еще до того, как клиент на него подписался, + /// то в момент подписки клиента будут вызваны соответсвующие события в синхронном + /// режиме и клиент будет оповещен в любом случае. Иначе, обработчики добавляются в + /// список в порядке подписания и в этом же порядке они будут вызваны при выполнении + /// обещания. + /// + /// + /// Обрабатывая результаты обещания можно преобразовывать результаты либо инициировать + /// связанные асинхронные операции, которые также возвращают обещания. Для этого следует + /// использовать соответствующую форму методе Then. + /// + /// + /// Также хорошим правилом является то, что Resolve и Reject должен вызывать + /// только инициатор обещания иначе могут возникнуть противоречия. + /// + /// + public class Promise { + + struct ResultHandlerInfo { + public ResultHandler resultHandler; + public ErrorHandler errorHandler; + } + + enum State { + Unresolved, + Resolving, + Resolved, + Cancelled + } + + LinkedList m_handlersChain = new LinkedList(); + State m_state; + bool m_cancellable; + T m_result; + Exception m_error; + + public Promise() { + m_cancellable = true; + } + + /// + /// Событие, возникающее при отмене асинхронной операции. + /// + /// + /// Как правило используется для оповещения объекта, выполняющего асинхронную операцию, о том, что ее следует отменить. + /// + public event EventHandler Cancelled; + + /// + /// Выполняет обещание, сообщая об успешном выполнении. + /// + /// Результат выполнения. + /// Данное обещание уже выполнено + public void Resolve(T result) { + lock (this) { + if (m_state == State.Cancelled) + return; + if (m_state != State.Unresolved) + throw new InvalidOperationException("The promise is already resolved"); + m_result = result; + m_state = State.Resolving; + } + + ResultHandlerInfo handler; + while (FetchNextHandler(out handler)) + InvokeHandler(handler); + } + + /// + /// Выполняет обещание, сообщая об ошибке + /// + /// Исключение возникшее при выполнении операции + /// Данное обещание уже выполнено + public void Reject(Exception error) { + lock (this) { + if (m_state == State.Cancelled) + return; + if (m_state != State.Unresolved) + throw new InvalidOperationException("The promise is already resolved"); + m_error = error; + m_state = State.Resolving; + } + + ResultHandlerInfo handler; + while (FetchNextHandler(out handler)) + InvokeHandler(handler); + } + + /// + /// Отменяет операцию, если это возможно. + /// + /// true Операция была отменена, обработчики не будут вызваны.false отмена не возможна, поскольку обещание уже выполнено и обработчики отработали. + public bool Cancel() { + lock(this) { + if (m_state == State.Unresolved && m_cancellable) { + m_state = State.Cancelled; + return true; + } else + return false; + } + } + + /// + /// Добавляет обработчики событий выполнения обещания. + /// + /// Обработчик успешного выполнения обещания. + /// Данному обработчику будет передан результат выполнения операции. + /// Обработчик ошибки. Данный обработчик получит + /// исключение возникшее при выполнении операции. + /// Само обещание + public Promise Then(ResultHandler success, ErrorHandler error) { + if (success == null && error == null) + return this; + + AddHandler(new ResultHandlerInfo() { + resultHandler = success, + errorHandler = error + }); + + return this; + } + + public Promise Then(ResultHandler success) { + return Then (success, null); + } + + public Promise Anyway(Action handler) { + if (handler == null) + return this; + AddHandler(new ResultHandlerInfo { + resultHandler = x => handler(), + errorHandler = x => handler() + }); + + return this; + } + + /// + /// Позволяет преобразовать результат выполения операции к новому типу. + /// + /// Новый тип результата. + /// Преобразование результата к новому типу. + /// Обработчик ошибки. Данный обработчик получит + /// исключение возникшее при выполнении операции. + /// Новое обещание, которое будет выполнено при выполнении исходного обещания. + public Promise Map(ResultMapper mapper, ErrorHandler error) { + if (mapper == null) + throw new ArgumentNullException("mapper"); + + // создаем прицепленное обещание + Promise chained = new Promise(); + + AddHandler(new ResultHandlerInfo() { + resultHandler = delegate(T result) { + try { + // если преобразование выдаст исключение, то сработает reject сцепленного deferred + chained.Resolve(mapper(result)); + } catch (Exception e) { + chained.Reject(e); + } + }, + errorHandler = delegate(Exception e) { + if (error != null) + error(e); + // в случае ошибки нужно передать исключение дальше по цепочке + chained.Reject(e); + } + }); + + return chained; + } + + public Promise Map(ResultMapper mapper) { + return Map (mapper, null); + } + + /// + /// Сцепляет несколько аснхронных операций. Указанная асинхронная операция будет вызвана после + /// выполнения текущей, а результат текущей операции может быть использован для инициализации + /// новой операции. + /// + /// Тип результата указанной асинхронной операции. + /// Асинхронная операция, которая должна будет начаться после выполнения текущей. + /// Обработчик ошибки. Данный обработчик получит + /// исключение возникшее при выполнении текуещй операции. + /// Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции. + public Promise Chain(ChainedOperation chained, ErrorHandler error) { + + // проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно + // создать посредника, к которому будут подвызяваться следующие обработчики. + // когда будет выполнена реальная асинхронная операция, она обратиться к посреднику, чтобы + // передать через него результаты работы. + Promise medium = new Promise(); + + AddHandler(new ResultHandlerInfo() { + resultHandler = delegate(T result) { + try { + chained(result).Then( + x => medium.Resolve(x), + e => medium.Reject(e) + ); + } catch(Exception e) { + // если сцепленное действие выдало исключение вместо обещания, то передаем ошибку по цепочке + medium.Reject(e); + } + }, + errorHandler = delegate(Exception e) { + if (error != null) + error(e); + // в случае ошибки нужно передать исключение дальше по цепочке + medium.Reject(e); + } + }); + + return medium; + } + + public Promise Chain(ChainedOperation chained) { + return Chain (chained, null); + } + + /// + /// Дожидается отложенного обещания и в случае успеха, возвращает + /// его, результат, в противном случае бросает исключение. + /// + /// + /// + /// Если ожидание обещания было прервано по таймауту, это не значит, + /// что обещание было отменено или что-то в этом роде, это только + /// означает, что мы его не дождались, однако все зарегистрированные + /// обработчики, как были так остались и они будут вызваны, когда + /// обещание будет выполнено. + /// + /// + /// Такое поведение вполне оправдано поскольку таймаут может истечь + /// в тот момент, когда началась обработка цепочки обработчиков, и + /// к тому же текущее обещание может стоять в цепочке обещаний и его + /// отклонение может привести к непрогнозируемому результату. + /// + /// + /// Время ожидания + /// Результат выполнения обещания + public T Join(int timeout) { + ManualResetEvent evt = new ManualResetEvent(false); + Anyway(() => evt.Set()); + + if (!evt.WaitOne(timeout, true)) + throw new TimeoutException(); + + if (m_error != null) + throw new TargetInvocationException( m_error ); + else + return m_result; + } + + public T Join() { + return Join(Timeout.Infinite); + } + + /// + /// Данный метод последовательно извлекает обработчики обещания и когда + /// их больше не осталось - ставит состояние "разрешено". + /// + /// Информация об обработчике + /// Признак того, что еще остались обработчики в очереди + bool FetchNextHandler(out ResultHandlerInfo handler) { + handler = default(ResultHandlerInfo); + + lock (this) { + Debug.Assert(m_state == State.Resolving); + + if (m_handlersChain.Count > 0) { + handler = m_handlersChain.First.Value; + m_handlersChain.RemoveFirst(); + return true; + } else { + m_state = State.Resolved; + return false; + } + } + } + + void AddHandler(ResultHandlerInfo handler) { + bool invokeRequired = false; + + lock (this) { + if (m_state != State.Resolved) + m_handlersChain.AddLast(handler); + else + invokeRequired = true; + } + + // обработчики не должны блокировать сам объект + if (invokeRequired) + InvokeHandler(handler); + } + + void InvokeHandler(ResultHandlerInfo handler) { + if (m_error == null) { + try { + if (handler.resultHandler != null) + handler.resultHandler(m_result); + } catch { } + } + + if (m_error != null) { + try { + if (handler.errorHandler !=null) + handler.errorHandler(m_error); + } catch { } + } + } + + + } +} diff --git a/Implab/Properties/AssemblyInfo.cs b/Implab/Properties/AssemblyInfo.cs new file mode 100644 --- /dev/null +++ b/Implab/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("Implab")] +[assembly: AssemblyDescription("Tools")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Implab")] +[assembly: AssemblyTrademark("")] +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/Local.testsettings b/Local.testsettings new file mode 100644 --- /dev/null +++ b/Local.testsettings @@ -0,0 +1,10 @@ + + + These are default test settings for a local test run. + + + + + + + \ No newline at end of file diff --git a/TraceAndTestImpact.testsettings b/TraceAndTestImpact.testsettings new file mode 100644 --- /dev/null +++ b/TraceAndTestImpact.testsettings @@ -0,0 +1,21 @@ + + + These are test settings for Trace and Test Impact. + + + + + + + + + + + + + + + + + + \ No newline at end of file