Jak praktycznie testować operacje sieciowe w boost.asio?

Jak praktycznie testować operacje sieciowe w boost.asio?
CY
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 20 godzin
  • Postów:109
0

Czołem,
jak mam testować takie operacje sieciowe np. czy klient rzeczywiście się łączy, odbiera/wysyła prawidłowe ilości danych itp... Jakieś mocki? Używaliście może test::stream, co sądzicie?

czy startowanie jakiegoś serwera testowego ma sens?

Macie jakieś rady gdzie zacząć albo jak wy to robicie?

edytowany 1x, ostatnio: Cyberah
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:2 minuty
2

Testy się pisze by sprawdzać własny kod.
Ergo piszesz swój kod tak, by boost::asio było twoją zależnością, którą wstrzykujesz do swojego kodu, a w testach wstrzykujesz wersję mock.

Ten test::stream trzeba by było wypróbować w boju, by stwierdzić czy to ma ręce i nogi.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 1x, ostatnio: MarekR22
CY
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 20 godzin
  • Postów:109
0

No tak, ale jak niby mam zrobić atrapę takiego socketa i na nim robić wszystkie operacje? O to mi chodzi - nie wiem z czym to się je.

edit:
czyli co, robić serwer testowy i na nim próbować popsuć swój kod?

edytowany 1x, ostatnio: Cyberah
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • Postów:1493
2

To jest złożony problem. Rozsądnie jest założyć, że asio czy jakiego tam innego frameworka używasz działa dobrze i tak jak @MarekR22 napisał, wstrzykujesz go jako zależność, a do testów mockujesz interfejs i sprawdzasz tylko czy funkcja send została wywołana ew., że receive zwróci jakiś tam bufor.
Przykład będzie nie asio, tylko QT, bo akurat taki mam pod ręką. Potrzebowałem czytać czujnik ćisnienia na UARTcie, ale chciałem być w stanie to testować bez ciągłego pompowania, no to:

Kopiuj
      MockedIoDevice mock{{0x01, 0x02, 0x0f, 0xf0, 0xf1, 0x1f}}; // testuję dla takich danych wejściowcyh
      mock.open(QIODevice::ReadOnly | QIODevice::Unbuffered);
      IODeviceReader serialPortReader{mock};

Tenże mock implementuje interfejs pozwalający readerowi czytać go jak uart:

Kopiuj
class MockedIoDevice : public QIODevice
{
    Q_OBJECT
public:
    MockedIoDevice(std::initializer_list<unsigned char> bytes, QObject* parent = nullptr);
    qint64 bytesAvailable() const override;
    qint64 size() const override;
    bool canReadLine() const override;
    void close() override { }
    bool isSequential() const override;
    bool atEnd() const override;
    qint64 pos() const override;
    bool seek(qint64 pos) override;

protected:
    qint64 readData(char* data, qint64 maxSize) override;
    qint64 writeData(const char* data, qint64 maxSize) override;
    qint64 readLineData(char* data, qint64 maxSize) override;

private:
    std::vector<unsigned char> m_readoutBuf; 
    decltype(m_readoutBuf)::const_iterator m_currentReadPos = m_readoutBuf.cbegin();
};

W Boost::Asio nie wiem czy będziesz miał analogiczny interfejs, ale podejrzewam, że będzie w stanie coś opędzić templatkami?

EDIT: no a jak nie możesz mockować to
a. robisz testy end-to-end. Ale to niekoniecznie dobrze.
b. Jeżeli jesteś na jakiejś platforme POSIXowej to możesz użyć LD_PRELOAD czy tam --wrap przekazanego do linkera i podmienić syscalle, ktorych ASIO używa.
https://stackoverflow.com/questions/46444052/how-to-wrap-functions-with-the-wrap-option-correctly

edytowany 2x, ostatnio: alagner
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:2 minuty
0

Temat jest bardzo szeroki.
Jak chcesz konkretnej pomocy, musisz skonkretyzować pytanie.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
CY
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 20 godzin
  • Postów:109
0

no ciężary mega napisać te testy, nie sądziłem, że będzie z tym tyle problemów.

Muszę przegrzebać gh i znaleźć jakieś projekty gdzie ludzie właśnie testują swoje funkcje wykorzystujące asio, bo muszę mieć jakiś ogólny zarys jak to ma wyglądać. Koniec końców dobiorę się do tego test streama, którego podesłałem i z nim będę się bawić, bo już dość czasu zmarnowałem na napisanie samego mocka serwera (który i tak nie działa XDD)

edytowany 1x, ostatnio: Cyberah
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • Postów:1493
0

Nie wiem na ile asio jest w headerach a na ile w libce. Jak jest raczej w libce a headery to czyste interfejsy (ale z boostem to nie jest oczywiste) to możesz spróbować mockować na etapie linkowania...

edytowany 1x, ostatnio: alagner
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0

Dla REST'a używam programu PostMan: https://www.postman.com/product/rest-client/
On potrafi zapisać sporą ilość ręcznie wpisanych zapytań oraz odpowiedzi na nie.
Po czym można go przestawić w tryb symulacji, i jak znajdzie odpowiednie zapytanie w zapamiętanych zapytaniach to odpowiada zapamiętaną odpowiedzią. jedynie się łączysz z localhost ale to można przestawić (pod windows.: c:\Windows\System32\drivers\etc\hosts)
Jeżeli to nie jest REST to co za problem napisać coś podobnego?
Zauważ że nie potrzebujesz tak rozbudowanych funkcjonalności jak PostMan


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:2 minuty
2
Cyberah napisał(a):

no ciężary mega napisać te testy, nie sądziłem, że będzie z tym tyle problemów.

Muszę przegrzebać gh i znaleźć jakieś projekty gdzie ludzie właśnie testują swoje funkcje wykorzystujące asio, bo muszę mieć jakiś ogólny zarys jak to ma wyglądać. Koniec końców dobiorę się do tego test streama, którego podesłałem i z nim będę się bawić, bo już dość czasu zmarnowałem na napisanie samego mocka serwera (który i tak nie działa XDD)

  1. Daj jakiś przykład swojego kodu do przetestowania to dostaniesz przykład
  2. Pisanie testów do gotowego kodu jest trudne i nieprzyjemne. Pisanie kodu do gotowych testów jesz szybkie i przyjemne.

Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
CY
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 20 godzin
  • Postów:109
0

@MarekR22: no dobra, to rzuć proszę okiem:
metoda, którą chcę przetestować wygląda mniej więcej tak:

Kopiuj
void Client::connect(asio::ip::address const& ip_address, unsigned short const  port) {
    asio::ip::tcp::endpoint ep{ ip_address, port };
    asio::ip::tcp::socket sock{ m_ioc }; //asio::io_context;
    sock.async_connect(ep,
        [this](auto const& ec) {
            on_connected(ec);
    });
}

on_connected() jest dość prosty:

Kopiuj
void Client::on_connected(system::error_code const& ec) {
    if (!ec)
        emit connected();
    else
        emit badConnect(ec);
}

i ja to dotychczas próbuje testować tak, że chcę złapać sygnał używając QSignalSpy:

Kopiuj
TEST(ClientTest, passingGoodArgumentsToClientConnectMethodProperlyConnects)
{
    asio::ip::address const ip_address{asio::ip::address::from_string("127.0.0.1")};
    auto const port{3333};

    Client client;
    QSignalSpy connectedEmitted{&client, SIGNAL(connected)};

    client.connect(ip_address, port); //boom! jak connectuje to crashuje mi test
    //EXPECT_EQ(connectedEmitted.count(), 1);

    //client.disconnect();
}

Serwer ofc w międzyczasie mam włączony i nasłuchuje na porcie 3333, ale i tak crashuje.
Nie mam już pomysłów :(

MarekR22
teraz nie mam za bardzo czasu, ale dziwi mnie, że używasz boost:asio, kiedy dysponujesz Qt! Nie gryzie ci się kontekst boost:asio z QEventLoop Qt?
CY
CY
w zasadzie to jestem miło zaskoczony, że to działa, bo rzeczywiście parę osób odradzało mieszanie boost, wątków i Qt5, bo może być buba, ale na razie jest git, a sygnały wysyłane z handlerów mogą być elegancko przetworzone dalej.
AL
No chyba jednak się gryzie jak crashuje. Sygnał leci z tego samego wątku co QT event loop?
CY
już powoli dochodzę do sedna problemu, generalnie pogmatwana sprawa, leciał inny sygnał niż spodziewany + muszę "zarejestrować" jeszcze go, bo sygnał przekazuje niestandardowy typ danych.
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
2

Coś mi się wydaje że pomyliłeś pisanie testów z debugowaniem.
Celem testów jest odpowiedz na jedno z dwóch pytań:

  • Czy już zaczęło działać poprawnie [T/N]?
  • Czy wciąż działa poprawnie [T/N]?

Natomiast skoro crashuje to już masz odpowiedź na to pytanie, szukać należy za pomocą debugiera.


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
Zobacz pozostałe 2 komentarze
_13th_Dragon
@revcorey, te interfajsy muszą być już gotowe przed rozpoczęciem pisania testów czyli formalnie nie mogą do tego służyć, Aczkolwiek dopiero jak siadasz testy pisać to wyłazi szydło z worka i czasami się okazuje że intefrasy zaprojektowano do bani, więc wracasz do korekty interfaców - no ale to bardzo rzadkie zjawisko.
RE
czy ja wiem. Pracowałem przy różnych softach w cpp gdzie wszystkie miały najmniej po 15-20 lat. Ilość słabo zaprojektowanych interfejsów porażała. Do tego klasy po 5 tysięcy linii itd. Moim zdaniem zdarza to się częściej niż człowiekowi się zdaje z resztą już robiłem sam refactoringi nie raz swojego kodu :P
_13th_Dragon
Czyli w tym przypadku interfacy gotowe, siadasz na szydło z worka ... to znaczy za pisanie testów ... dalej jak wyżej.
RE
chodziło mi o to że już ktoś klepnął te interfejsy wcześniej(nawet ut były czasami!) ale to i tak nie pomogło :D jak to mówią życie życie jest nowelą
_13th_Dragon
No jasne nie neguję tego, tylko że testy nie pomagają tworzyć poprawnych interfacow, lecz process pisania testów wskazuje na niepoprawnie zaprojektowane interfacy. Może taka alegoria: - Process robienia dziecka czasami wskazuje że facet nie potrafi zadowolić partnerki nie zaś dziecko które w wyniku tego się urodzi.
CY
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 20 godzin
  • Postów:109
0

Dobra, po przerwie ogarnąłem to, ale rozwiązanie ciągnie za sobą dwie wady: serwer musi być dostępny(na razie nie napisałem jeszcze mocka) i przede wszystkim muszę usypiać wątek na parę milisekund. Co myślicie? Nie wiem czy usypianie testów to dobry pomysł, bo jak ich się uzbiera to będą się długo wątki lenić.

AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • Postów:1493
0

Myślimy, że to jest co najmniej code smell. Tzn. ja myślę za siebie ;) ale mam wrazenie, że pozostali Panowie się zgodzą.
Czemu w sumie musi coś spać?

CY
  • Rejestracja:prawie 7 lat
  • Ostatnio:około 20 godzin
  • Postów:109
0

bo klient asynchronicznie się łączy z serwerem i jak nie uśpię go na ~5 milisekund, to zwyczajnie nie zdąży wywołać handlera po połączeniu (który wysyła sygnały) :

Kopiuj
    client->connect(ip_address, port);
    std::this_thread::sleep_for(std::chrono::milliseconds(5));

    EXPECT_EQ(connectedEmitted.count(), 1);
    EXPECT_EQ(badConnectEmitted.count(), 0);

connect od razu zwraca, bo to praktycznie tylko asio::async_connect().

generalnie pewnie da się to lepiej zrobić ze zmiennymi warunkowymi, ale to jeszcze muszę rozkminić.

edytowany 1x, ostatnio: Cyberah
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 3 lata
  • Postów:1493
1

Ale QSignalSpy przeca ma wait? https://doc.qt.io/qt-5/qsignalspy.html#wait

CY
o kurdebele rzeczywiście, dzięki!
CY
spoko, ale gryzie się z asio :PP
AL
jak dokładnie się gryzie? Z jakiego wątku sygnał leci?
CY
dostaję coś takiego: QEventLoop: Cannot be used without QApplication i QObject::startTimer: Timers can only be used with threads started with QThread, nie wiem czy warto coś z tym robić, ale ciekawe. Jutro jak już to się tym zajmę, bo już mi bania wysiada
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:2 minuty
3
MarekR22 && Cyberah skomentował(a):
  • Nie gryzie ci się kontekst boost:asio z QEventLoop Qt? — MarekR22
  • ani trochę:P — Cyberah
  • w zasadzie to jestem miło zaskoczony, że to działa, bo rzeczywiście parę osób odradzało mieszanie boost, wątków i Qt5, bo może być buba, ale na razie jest git, a sygnały wysyłane z handlerów mogą być elegancko przetworzone dalej. — Cyberah

Żeby było jasnet zarówno QEventLoop jak boost::asion::io_context robią to samo.

  • QEventLoop wywoławane jest przez QApplication:exec() i blokuje wątek, aż do nadejścia jakiegoś zdarzenia od systemu.
  • tak samo boost::asion::io_context::run - blokuje wątek aż do nadejścia zdarzeń od systemu.

Ergo oba nie mogą działać na jednym wątku, bo jeden be∂zie blokować drugi.
Jeśli boost::asion::io_context::run robisz na osobnym wątku, to jest szansa, że to działa. Niestety z moich obserwacji wynika, że większość developerów z medium level nie ogarnia wielowątkowości, a beginning wydaje się tylko, że umieją wielowątkowość.

Co do samego testu. To może być coś w tym stylu (pisane z palca).

Kopiuj
class IAsioSocket {
public:
    virtual ~IAsioSocket() {}

    virtual void async_connect(const endpoint_type & peer_endpoint, ConnectHandler && handler) = 0;
};

class INetworkDependencies {
public:
    virtual ~INetworkDependencies() {}

    virtual std::unique_ptr<IAsioSocket> makeSocket() = 0;
};

class Client {
public:
    explicit Client(INetworkDependencies *dependencies)
        : m_dependencies{dependencies}
        , m_socket{m_dependencies->makeSocket()}
    {}

    void connect(asio::ip::address const& ip_address, unsigned short const  port) {
        asio::ip::tcp::endpoint ep{ ip_address, port };
        m_socket->async_connect(ep,
            [this](auto const& ec) {
                on_connected(ec);
        });
    }

    void on_connected(system::error_code const& ec) {
        if (!ec)
            emit connected();
        else
            emit badConnect(ec);
    }

signal:
    void connected();
    void badConnect(system::error_code const& ec);

private:
    INetworkDependencies *m_dependencies;
    std::unique_ptr<IAsioSocket> m_socket;
};
Kopiuj
class MockAsioSocket : public IAsioSocket {
public:
    MOCK_METHOD(void, async_connect, (const endpoint_type & peer_endpoint, ConnectHandler && handler), (override));
};

class MockNetworkDependencies : public INetworkDependencies {
public:
    MOCK_METHOD(std::unique_ptr<IAsioSocket>, makeSocket, (), (override));
};
Kopiuj
class ClientTest : public testing::Test
{
public:
     void SetUp() override
     {
         constructClient();
     }

     void constructClient()
     {
           EXPECT_CALL(mockAsio, makeSocket()).WillOnce(Invoke([this] {
                 auto socket = std::make_unique<MockAsioSocket>();
                 mockSocket = socket.get();
                 return socket;
           }));

           client = std::make_unique<Client>(&mockAsio);
           ASSERT_TRUE(Mock::VerifyAndClearExpectations(&mockAsio));

          errorSpy = std::make_unique<QSignalSpy>(client, SIGNAL(badConnect(const system::error_code&)));
          successSpy = std::make_unique<QSignalSpy>(client, SIGNAL(connected()));
     }

     void checkNoSignals()
     {
         ASSERT_EQ(errorSpy.count(), 0);
         ASSERT_EQ(successSpy.count(), 0);
     }

     void startConnecting()
     {
           EXPECT_CALL(*mockSocket, async_connect(_, _))
                 .WillOnce(SaveArg<1>(&connectHandler));
           client->connect(TestAddress, TestPort);
           ASSERT_TRUE(Mock::VerifyAndClearExpectations(mockSocket));
     }

     void successfullConnect()
     {
         ASSERT_TRUE(!!connectHandler);
         ASSERT_EQ(errorSpy.count(), 0);
         ASSERT_EQ(successSpy.count(), 0);

         connectHandler(TestSuccessValue);

         ASSERT_EQ(errorSpy.count(), 0);
         ASSERT_EQ(successSpy.count(), 1);
     }

     MockNetworkDependencies mockAsio;
     MockAsioSocket *mockSocket = nullptr;

     std::unique_ptr<QSignalSpy> errorSpy;
     std::unique_ptr<QSignalSpy> successSpy;

     std::unique_ptr<Client> client;
     ConnectHandler connectHandler;
};

class ClientConstructedTest : public TestClientTest
{};

TEST_F(ClientConstructedTest, connecInvokesBoostAsioAsyncConnect)
{
     ASSERT_NO_FATAL_FAILURE(startConnecting());
}

class ConnectingClientTest : public TestClientTest
{
    void SetUp() overide
    {
          TestClientTest::SetUp();
          ASSERT_NO_FATAL_FAILURE(startConnecting());
    }
};


TEST_F(ConnectingClientTest, whenAsioReportsErrorClientReportsError)
{
     ASSERT_NO_FATAL_FAILURE(checkNoSignals());
     connectHandler(TestErrorValue);

     ASSERT_EQ(errorSpy.count(), 1);
     ASSERT_EQ(successSpy.count(), 0);
}


TEST_F(ConnectingClientTest, whenAsioReportsSuccessClientEntersConnectedState)
{
     ASSERT_NO_FATAL_FAILURE(checkNoSignals());
     ASSERT_NO_FATAL_FAILURE(successfullConnect());
}

Swoją drogą, to że asio::ip::tcp::socket sock{ m_ioc } masz jako zmienną lokalną, kiedy używasz asynchronicznego API to dość poważny błąd.
W moim kodzie jest to poprawione.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 2x, ostatnio: MarekR22
CY
o kurde, ale żeś posta naskrobał, jutro będę analizował, dzięki wielkie. edit: generalnie nie jest to sam sock lokalny, ale struktura, która właśnie podtrzymuje życie, ale dla uproszczenia zawęziłem to do samego socka. w sumie też crtp robi robotę jeśli chodzi o podtrzymywanie życia zmiennej.
MarekR22
na samym początku kod testów rośnie dużo szybciej niż kod produkcyjny. Ale jak się dobrze napisze funkcje pomocnicze (ja sobie je gromadzę w klasie bazowej testu), to potem dopisanie kolejnego skomplikowanego testu to parę linijek. Klasy pochodne testu reprezentują stan w jakim obecnie znajduje się testowana klasa (skontrowana, w trakcie łączenia, połączona), dzięki temu łatwiej się czyta, co test sprawdza.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)