Skip to content

File environment_interface_record.hpp

File List > backends > cxx > include > zmbt > model > environment_interface_record.hpp

Go to the documentation of this file

#ifndef ZMBT_MODEL_ENVIRONMENT_INTERFACE_RECORD_HPP_
#define ZMBT_MODEL_ENVIRONMENT_INTERFACE_RECORD_HPP_


#include <chrono>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <thread>
#include <tuple>
#include <typeindex>
#include <type_traits>

#include "environment.hpp"
#include "zmbt/expr/expression.hpp"




namespace zmbt {



class Environment::InterfaceHandle
{

private:
    object_id refobj_;
    interface_id interface_;

protected:

    mutable zmbt::Environment env;
    std::shared_ptr<OutputRecorder> output_recorder_;
public:


    interface_id interface() const
    {
        return interface_;
    }

    object_id refobj() const
    {
        return refobj_;
    }

    InterfaceHandle(Environment const& e, interface_id const& interface, object_id refobj);
    InterfaceHandle(Environment const& e, boost::json::string_view ref);

  public:

    InterfaceHandle(interface_id const& interface, object_id refobj);

    InterfaceHandle(interface_id const& interface, nullptr_t);

    InterfaceHandle(boost::json::string_view ref);


    template<class H, class E = mp_if<mp_not<is_pointer<H>>, void>>
    InterfaceHandle(interface_id const& interface, H const& obj)
        : InterfaceHandle(interface, std::addressof(obj))
    {
    }

    InterfaceHandle(InterfaceHandle const&) = default;
    InterfaceHandle(InterfaceHandle && o) = default;
    InterfaceHandle& operator=(InterfaceHandle const&) = default;
    InterfaceHandle& operator=(InterfaceHandle &&) = default;
    ~InterfaceHandle() = default;

    Environment& Env() const
    {
        return env;
    }

    boost::json::value PrototypeReturn() const;

    boost::json::array PrototypeArgs() const;

    void EnableOutputRecordFor(ChannelKind const kind);

    void MaybeThrowException();

    boost::json::value YieldInjection(ChannelKind const kind);

    boost::json::value YieldInjectionArgs()
    {
        return YieldInjection(ChannelKind::Args);
    }

    boost::json::value YieldInjectionReturn()
    {
        return YieldInjection(ChannelKind::Return);
    }

    void Inject(std::shared_ptr<Generator> gen, lang::Expression const& tf, ChannelKind const kind, boost::json::string_view jp = "");



    void InjectReturn(lang::Expression const& e, boost::json::string_view jp = "")
    {
        return Inject(std::make_shared<Generator>(e), expr::Noop, ChannelKind::Return, jp);
    }

    void InjectArgs(lang::Expression const& e, boost::json::string_view jp = "")
    {
        return Inject(std::make_shared<Generator>(e), expr::Noop, ChannelKind::Args, jp);
    }


    std::size_t ObservedCalls() const;



    boost::json::array CaptureSlice(boost::json::string_view signal_path) const;

    boost::json::array const& Captures() const;

    boost::json::string const& key() const;

    InterfaceHandle& RunAsAction();

    InterfaceHandle& RunAsTrigger(std::size_t const repeats = 1);
};

template <class Interface>
class Environment::TypedInterfaceHandle : public Environment::InterfaceHandle
{

    using reflection  =  reflect::invocation<Interface const&>;
    using return_t    = typename reflection::return_t;
    using args_t      = typename reflection::args_t;
    using unqf_args_t = tuple_unqf_t<args_t>;

    using return_or_nullptr_t = reflect::invocation_ret_unqf_or_nullptr_t<Interface const&>;


    template <class T>
    using rvalue_reference_to_value = mp_if<std::is_rvalue_reference<T>, std::remove_reference_t<T>, T>;

    using hookout_args_t = mp_transform<rvalue_reference_to_value, args_t>;

    void HookArgsImpl(hookout_args_t & args)
    try
    {
        output_recorder_->push(convert_tuple_to<unqf_args_t>(args), ErrorOr<return_or_nullptr_t>());


        auto const injection = YieldInjection(ChannelKind::Args).as_array();

        if (injection.size() != std::tuple_size<unqf_args_t>())
        {
            env.SetTestError({
                {"error"    , "invalid inject arguments arity"},
                {"injection", injection},
                {"interface", interface().str()               },
                {"context"  , "Hook"                          },
                {"injection", injection                       },
            });
        }
        else
        {
            auto args_out = dejsonize<unqf_args_t>(injection);
            tuple_exchange(args, args_out);
        }
    }
    catch(const std::exception& e)
    {
        env.SetTestError({
            {"error"    , "exception thrown at args evaluation"},
            {"interface", interface().str()                    },
            {"context"  , "Hook"                               },
            {"what"     , e.what()                             },
        });
    }


    void HookReturnImpl(type_tag<void>)
    {
    }

    template <class T>
    auto HookReturnImpl(type_tag<T>) -> mp_if<mp_not<is_reference<T>>, T>
    try
    {
        boost::json::value result(YieldInjection(ChannelKind::Return));
        return dejsonize<T>(result);
    }
    catch(const std::exception& e)
    {
        env.SetTestError({
            {"error"    , "exception thrown at return evaluation"},
            {"interface", interface().str()                      },
            {"context"  , "Hook"                                 },
            {"what"     , e.what()                               },
        });
        return dejsonize<T>(PrototypeReturn());
    }

    template <class T>
    auto HookReturnImpl(type_tag<T>) -> mp_if<is_reference<T>, T>
    {
        using TT = remove_cvref_t<T>;

        TT value = HookReturnImpl(type<TT>);
        auto const key = format("$(ret-ref-%s-%s)", interface().key(), refobj().key());
        TT& ref = Env().template GetSharedRef<TT>(key, reflect::signal_traits<TT>::init());
        ref = value;
        return ref;
    }



    public:


    template <class H>
    TypedInterfaceHandle(interface_id const& interface, H const& refobj)
        : Environment::InterfaceHandle(interface, refobj)
    {
    }

    TypedInterfaceHandle(TypedInterfaceHandle const&) = default;
    TypedInterfaceHandle(TypedInterfaceHandle &&) = default;
    ~TypedInterfaceHandle() = default;

    return_t Hook(hookout_args_t args)
    {
        std::size_t nofcall;
        boost::json::value result;
        HookArgsImpl(args);
        MaybeThrowException();
        return HookReturnImpl(type<return_t>);
    }

    template <class... A>
    return_t Hook(A&&... arg)
    {
        hookout_args_t args {arg...};
        return Hook(args);
    }
};


template <class Interface>
Environment::TypedInterfaceHandle<Interface> InterfaceRecord(Interface const& interface, object_id const& obj = {ifc_host_nullptr<Interface>})
{
    Environment env {};
    env.InitializeInterfaceHandlers(interface);
    return {interface, obj};
}


}  // namespace zmbt

#endif  // ZMBT_MAPPING_ENVIRONMENT_INTERFACE_RECORD_HPP_